package user

import (
	"context"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/yaoapp/gou/process"
	"github.com/yaoapp/gou/session"
	"github.com/yaoapp/kun/exception"
	"github.com/yaoapp/kun/log"
	"github.com/yaoapp/kun/maps"
	"github.com/yaoapp/yao/openapi/oauth"
	"github.com/yaoapp/yao/openapi/oauth/authorized"
	"github.com/yaoapp/yao/openapi/oauth/providers/user"
	oauthtypes "github.com/yaoapp/yao/openapi/oauth/types"
	"github.com/yaoapp/yao/openapi/response"
	"github.com/yaoapp/yao/openapi/utils"
)

// User Profile Management Handlers

// GinProfileGet handles GET /profile - Get current user profile
func GinProfileGet(c *gin.Context) {
	// Get authorized user info
	authInfo := authorized.GetInfo(c)
	if authInfo == nil || authInfo.UserID == "" {
		errorResp := &response.ErrorResponse{
			Code:             response.ErrInvalidClient.Code,
			ErrorDescription: "User not authenticated",
		}
		response.RespondWithError(c, response.StatusUnauthorized, errorResp)
		return
	}

	// Parse query parameters
	var req ProfileGetRequest
	if err := c.ShouldBindQuery(&req); err != nil {
		// Ignore binding errors for optional parameters
		req = ProfileGetRequest{}
	}

	// Call business logic to get profile
	profile, err := profileGet(c.Request.Context(), authInfo.UserID, authInfo.TeamID, req)
	if err != nil {
		log.Error("Failed to get user profile: %v", err)
		errorResp := &response.ErrorResponse{
			Code:             response.ErrServerError.Code,
			ErrorDescription: "Failed to retrieve user profile",
		}
		response.RespondWithError(c, response.StatusInternalServerError, errorResp)
		return
	}

	// Return user profile
	response.RespondWithSuccess(c, http.StatusOK, profile)
}

// GinProfileUpdate handles PUT /profile - Update current user profile
func GinProfileUpdate(c *gin.Context) {
	// Get authorized user info
	authInfo := authorized.GetInfo(c)
	if authInfo == nil || authInfo.UserID == "" {
		errorResp := &response.ErrorResponse{
			Code:             response.ErrInvalidClient.Code,
			ErrorDescription: "User not authenticated",
		}
		response.RespondWithError(c, response.StatusUnauthorized, errorResp)
		return
	}

	// Parse request body
	var req ProfileUpdateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		errorResp := &response.ErrorResponse{
			Code:             response.ErrInvalidRequest.Code,
			ErrorDescription: fmt.Sprintf("Invalid request body: %s", err.Error()),
		}
		response.RespondWithError(c, response.StatusBadRequest, errorResp)
		return
	}

	// Call business logic to update profile
	result, err := profileUpdate(c.Request.Context(), authInfo.UserID, req)
	if err != nil {
		log.Error("Failed to update user profile: %v", err)
		errorResp := &response.ErrorResponse{
			Code:             response.ErrServerError.Code,
			ErrorDescription: "Failed to update user profile",
		}
		response.RespondWithError(c, response.StatusInternalServerError, errorResp)
		return
	}

	// Return response with user_id and message
	response.RespondWithSuccess(c, http.StatusOK, result)
}

// Yao Process Handlers (for Yao application calls)

// ProcessProfileGet user.profile.get Profile get processor
// Args[0] (optional) map: {"team": true, "member": true, "type": true}
// Return: map: User profile data
func ProcessProfileGet(process *process.Process) interface{} {
	// Get user_id from session
	userIDStr := GetUserIDFromSession(process)

	// Get context
	ctx := process.Context
	if ctx == nil {
		ctx = context.Background()
	}

	// Get team_id from session if available
	teamID := ""
	if teamIDVal, err := session.Global().ID(process.Sid).Get("__team_id"); err == nil && teamIDVal != nil {
		if teamIDStr, ok := teamIDVal.(string); ok {
			teamID = teamIDStr
		}
	}

	// Parse options
	req := ProfileGetRequest{}
	if process.NumOfArgs() > 0 {
		opts := process.ArgsMap(0)
		if v, ok := opts["team"].(bool); ok {
			req.Team = v
		}
		if v, ok := opts["member"].(bool); ok {
			req.Member = v
		}
		if v, ok := opts["type"].(bool); ok {
			req.Type = v
		}
	}

	// Call business logic
	result, err := profileGet(ctx, userIDStr, teamID, req)
	if err != nil {
		exception.New("failed to get profile: %s", 500, err.Error()).Throw()
	}

	return result
}

// ProcessProfileUpdate user.profile.update Profile update processor
// Args[0] map: Profile update data (only profile fields allowed)
// Return: map: Updated user profile data
func ProcessProfileUpdate(process *process.Process) interface{} {
	// Get user_id from session
	userIDStr := GetUserIDFromSession(process)

	// Get context
	ctx := process.Context
	if ctx == nil {
		ctx = context.Background()
	}

	// Parse update data from first argument
	if process.NumOfArgs() == 0 {
		exception.New("profile update data is required", 400).Throw()
	}

	updateData := process.ArgsMap(0)
	req := buildProfileUpdateRequest(updateData)

	// Call business logic
	result, err := profileUpdate(ctx, userIDStr, req)
	if err != nil {
		exception.New("failed to update profile: %s", 500, err.Error()).Throw()
	}

	return result
}

// Private Business Logic Functions (internal use only)

// profileGet handles the business logic for getting user profile
func profileGet(ctx context.Context, userID, teamID string, req ProfileGetRequest) (maps.MapStrAny, error) {
	// Get user provider instance
	provider, err := getUserProvider()
	if err != nil {
		return nil, fmt.Errorf("failed to get user provider: %w", err)
	}

	// Get user data
	userData, err := provider.GetUser(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve user data: %w", err)
	}

	// Get Yao client config
	yaoClientConfig := GetYaoClientConfig()

	// Get or create subject
	subject, err := oauth.OAuth.Subject(yaoClientConfig.ClientID, userID)
	if err != nil {
		log.Warn("Failed to get user subject: %s", err.Error())
		subject = userID // Fallback to user ID
	}

	// Prepare OIDC user info (same format as login response)
	oidcUserInfo := oauthtypes.MakeOIDCUserInfo(userData)
	oidcUserInfo.Sub = subject
	oidcUserInfo.YaoUserID = userID

	// Add team and member info if requested and team_id is provided
	if teamID != "" && (req.Team || req.Member) {
		addTeamInfo(ctx, provider, oidcUserInfo, teamID, userID, req.Team, req.Member)
	}

	// Add type information if requested
	if req.Type {
		addTypeInfo(ctx, provider, oidcUserInfo, userData, teamID, userID)
	}

	// Convert to map for response (this will include all Yao fields)
	profileData := oidcUserInfo.Map()

	// Add member as separate object if requested (not part of OIDCUserInfo structure)
	if req.Member && teamID != "" {
		member, err := provider.GetMember(ctx, teamID, userID)
		if err == nil && member != nil {
			profileData["member"] = member
		}
	}

	return profileData, nil
}

// addTeamInfo adds team information to the profile
func addTeamInfo(ctx context.Context, provider *user.DefaultUser, oidcUserInfo *oauthtypes.OIDCUserInfo, teamID, userID string, withTeam, withMember bool) {
	// Only fetch team if either team or member info is requested
	if !withTeam && !withMember {
		return
	}

	// Get team details
	team, err := provider.GetTeamByMember(ctx, teamID, userID)
	if err != nil {
		log.Warn("Failed to get team: %v", err)
		return
	}

	// Add team info to OIDCUserInfo if requested
	if withTeam {
		oidcUserInfo.YaoTeamID = teamID
		oidcUserInfo.YaoTeam = &oauthtypes.OIDCTeamInfo{
			TeamID:      utils.ToString(team["team_id"]),
			Name:        utils.ToString(team["name"]),
			Description: utils.ToString(team["description"]),
			Logo:        utils.ToString(team["logo"]),
			OwnerID:     utils.ToString(team["owner_id"]),
		}

		// Check if user is owner
		if oidcUserInfo.YaoTeam.OwnerID == userID {
			isOwner := true
			oidcUserInfo.YaoIsOwner = &isOwner
		}

		// Add tenant_id if available
		if tenantID := utils.ToString(team["tenant_id"]); tenantID != "" {
			oidcUserInfo.YaoTenantID = tenantID
		}
	}
}

// addTypeInfo adds type information to the profile
func addTypeInfo(ctx context.Context, provider *user.DefaultUser, oidcUserInfo *oauthtypes.OIDCUserInfo, userData maps.MapStr, teamID, userID string) {
	var typeID string

	// Team context - try to get team's type first
	if teamID != "" {
		team, err := provider.GetTeamByMember(ctx, teamID, userID)
		if err == nil && team != nil {
			typeID = utils.ToString(team["type_id"])
		}
	}

	// Fallback to user's type
	if typeID == "" {
		typeID = utils.ToString(userData["type_id"])
	}

	if typeID == "" {
		return
	}

	oidcUserInfo.YaoTypeID = typeID

	// Get type details
	typeInfo, err := provider.GetType(ctx, typeID)
	if err != nil {
		log.Warn("Failed to get type: %v", err)
		return
	}

	oidcUserInfo.YaoType = &oauthtypes.OIDCTypeInfo{
		TypeID: utils.ToString(typeInfo["type_id"]),
		Name:   utils.ToString(typeInfo["name"]),
		Locale: utils.ToString(typeInfo["locale"]),
	}
}

// profileUpdate handles the business logic for updating user profile
func profileUpdate(ctx context.Context, userID string, req ProfileUpdateRequest) (ProfileUpdateResponse, error) {
	// Get user provider instance
	provider, err := getUserProvider()
	if err != nil {
		return ProfileUpdateResponse{}, fmt.Errorf("failed to get user provider: %w", err)
	}

	// Build update data map (only include non-nil fields)
	updateData := make(map[string]interface{})

	if req.Name != nil {
		updateData["name"] = *req.Name
	}
	if req.GivenName != nil {
		updateData["given_name"] = *req.GivenName
	}
	if req.FamilyName != nil {
		updateData["family_name"] = *req.FamilyName
	}
	if req.MiddleName != nil {
		updateData["middle_name"] = *req.MiddleName
	}
	if req.Nickname != nil {
		updateData["nickname"] = *req.Nickname
	}
	if req.Profile != nil {
		updateData["profile"] = *req.Profile
	}
	if req.Picture != nil {
		updateData["picture"] = *req.Picture
	}
	if req.Website != nil {
		updateData["website"] = *req.Website
	}
	if req.Gender != nil {
		updateData["gender"] = *req.Gender
	}
	if req.Birthdate != nil {
		updateData["birthdate"] = *req.Birthdate
	}
	if req.Zoneinfo != nil {
		updateData["zoneinfo"] = *req.Zoneinfo
	}
	if req.Locale != nil {
		updateData["locale"] = *req.Locale
	}
	if req.Address != nil {
		updateData["address"] = req.Address
	}
	if req.Theme != nil {
		updateData["theme"] = *req.Theme
	}
	if req.Metadata != nil {
		updateData["metadata"] = req.Metadata
	}

	// If no fields to update, return error
	if len(updateData) == 0 {
		return ProfileUpdateResponse{}, fmt.Errorf("no fields to update")
	}

	// Update user profile
	if err := provider.UpdateUser(ctx, userID, updateData); err != nil {
		return ProfileUpdateResponse{}, fmt.Errorf("failed to update user profile: %w", err)
	}

	// Return response with user_id and message
	return ProfileUpdateResponse{
		UserID:  userID,
		Message: "Profile updated successfully",
	}, nil
}

// buildProfileUpdateRequest converts a map to ProfileUpdateRequest
func buildProfileUpdateRequest(data map[string]interface{}) ProfileUpdateRequest {
	req := ProfileUpdateRequest{}

	if v, ok := data["name"].(string); ok {
		req.Name = &v
	}
	if v, ok := data["given_name"].(string); ok {
		req.GivenName = &v
	}
	if v, ok := data["family_name"].(string); ok {
		req.FamilyName = &v
	}
	if v, ok := data["middle_name"].(string); ok {
		req.MiddleName = &v
	}
	if v, ok := data["nickname"].(string); ok {
		req.Nickname = &v
	}
	if v, ok := data["profile"].(string); ok {
		req.Profile = &v
	}
	if v, ok := data["picture"].(string); ok {
		req.Picture = &v
	}
	if v, ok := data["website"].(string); ok {
		req.Website = &v
	}
	if v, ok := data["gender"].(string); ok {
		req.Gender = &v
	}
	if v, ok := data["birthdate"].(string); ok {
		req.Birthdate = &v
	}
	if v, ok := data["zoneinfo"].(string); ok {
		req.Zoneinfo = &v
	}
	if v, ok := data["locale"].(string); ok {
		req.Locale = &v
	}
	if v, ok := data["address"].(map[string]interface{}); ok {
		req.Address = v
	}
	if v, ok := data["theme"].(string); ok {
		req.Theme = &v
	}
	if v, ok := data["metadata"].(map[string]interface{}); ok {
		req.Metadata = v
	}

	return req
}

// GinProfileProviders returns the current user's linked OAuth accounts
func GinProfileProviders(c *gin.Context) {
	authInfo := authorized.GetInfo(c)
	if authInfo == nil || authInfo.UserID == "" {
		response.RespondWithError(c, response.StatusUnauthorized, &response.ErrorResponse{
			Code:             response.ErrInvalidClient.Code,
			ErrorDescription: "User not authenticated",
		})
		return
	}

	provider, err := getUserProvider()
	if err != nil {
		response.RespondWithError(c, response.StatusInternalServerError, &response.ErrorResponse{
			Code:             response.ErrServerError.Code,
			ErrorDescription: "Failed to get user provider",
		})
		return
	}

	accounts, err := provider.GetUserOAuthAccounts(c.Request.Context(), authInfo.UserID)
	if err != nil {
		log.Error("Failed to get OAuth accounts for user %s: %v", authInfo.UserID, err)
		response.RespondWithError(c, response.StatusInternalServerError, &response.ErrorResponse{
			Code:             response.ErrServerError.Code,
			ErrorDescription: "Failed to retrieve linked accounts",
		})
		return
	}

	if accounts == nil {
		accounts = []maps.MapStrAny{}
	}

	for i := range accounts {
		if email, ok := accounts[i]["email"].(string); ok && email != "" {
			accounts[i]["email"] = MaskEmail(email)
		}
	}

	response.RespondWithSuccess(c, response.StatusOK, accounts)
}
