package controllers

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"sort"
	"strings"
	"time"

	"ems-backend/models"
	"ems-backend/utils"
	"net/http"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

const (
	SSOAppID     = "app_e3d1afe340085e24"
	SSOAppSecret = "xnbJjZghQzZ0gPkc7e6ngirrAZq0oXum"
	SSOValidateURL = "https://api.hnyunzhu.com/api/v1/simple/validate"
)

type SSOUserStruct struct {
	Username string
	Name     string
	Email    string
}

// FindOrCreateUserFromSSO handles user creation for SSO login
func FindOrCreateUserFromSSO(ssoUser SSOUserStruct) (*models.User, error) {
	var user models.User

	// 1. Check if user exists by unique identifier
	err := models.DB.Where("username = ?", ssoUser.Username).First(&user).Error

	if err == nil {
		// User exists
		return &user, nil
	}

	if err == gorm.ErrRecordNotFound {
		// 2. User not found, create a NEW user without permissions
		newUser := models.User{
			Username: ssoUser.Username,
			Name:     ssoUser.Name,
			Status:   "0", // Normal status
		}

		if ssoUser.Email != "" {
			email := ssoUser.Email
			newUser.Email = &email
		}

		if err := models.DB.Create(&newUser).Error; err != nil {
			return nil, err
		}

		return &newUser, nil
	}

	return nil, err
}

type LoginRequest struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

type LoginResponse struct {
	Token string      `json:"token"`
	User  models.User `json:"user"`
}

func Login(c *gin.Context) {
	var req LoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
		return
	}

	// 1. Database User Check
	var user models.User
	// 使用 MD5 或其他加密方式比较密码
	if err := models.DB.Where("username = ? AND password = ?", req.Username, req.Password).First(&user).Error; err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
		return
	}

	token, err := utils.GenerateToken(user)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
		return
	}

	// Avoid returning sensitive data
	user.Password = ""

	c.JSON(http.StatusOK, LoginResponse{
		Token: token,
		User:  user,
	})
}

func GetProfile(c *gin.Context) {
	userId, exists := c.Get("userId")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	var user models.User
	if err := models.DB.Where("id = ?", userId).First(&user).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	user.Password = ""
	c.JSON(http.StatusOK, gin.H{"user": user})
}

type UpdatePwdRequest struct {
	OldPassword string `json:"oldPassword" binding:"required"`
	NewPassword string `json:"newPassword" binding:"required"`
}

func UpdateProfilePwd(c *gin.Context) {
	userId, exists := c.Get("userId")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	var req UpdatePwdRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var user models.User
	if err := models.DB.Where("id = ?", userId).First(&user).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// Verify old password (plaintext for now)
	if user.Password != req.OldPassword {
		c.JSON(http.StatusBadRequest, gin.H{"error": "旧密码错误"})
		return
	}

	user.Password = req.NewPassword
	if err := models.DB.Save(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update password"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"})
}

type SSOLoginRequest struct {
	Ticket string `json:"ticket" binding:"required"`
}

type SSOResponse struct {
	Valid       bool   `json:"valid"`
	UserID      int    `json:"user_id"`
	Mobile      string `json:"mobile"`
	MappedKey   string `json:"mapped_key"`
	MappedEmail string `json:"mapped_email"`
}

func generateSSOSignature(params map[string]interface{}, secret string) string {
	// 1. Sort keys
	keys := make([]string, 0, len(params))
	for k := range params {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// 2. Concatenate
	var sb strings.Builder
	for _, k := range keys {
		if k == "sign" {
			continue
		}
		// Filter out nil/empty if needed, but docs say "filter empty values and sign"
		// Assuming non-empty for simplicity or add check
		val := params[k]
		if val == nil {
			continue
		}
		
		strVal := fmt.Sprintf("%v", val)
		if strVal == "" {
			continue
		}

		if sb.Len() > 0 {
			sb.WriteString("&")
		}
		sb.WriteString(k + "=" + strVal)
	}
	
	// Debug log: String to sign
	fmt.Printf("SSO Signing String: %s\n", sb.String())

	// 3. HMAC-SHA256
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(sb.String()))
	return hex.EncodeToString(h.Sum(nil))
}

func SSOLogin(c *gin.Context) {
	var req SSOLoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
		return
	}

	// 1. Build validation request
	timestamp := time.Now().Unix()
	payload := map[string]interface{}{
		"app_id":    SSOAppID,
		"ticket":    req.Ticket,
		"timestamp": timestamp,
	}
	
	// Calculate signature
	sign := generateSSOSignature(payload, SSOAppSecret)
	payload["sign"] = sign

	// 2. Call UAP Validate API
	client := &http.Client{Timeout: 10 * time.Second}
	jsonBody, _ := json.Marshal(payload)
	
	resp, err := client.Post(SSOValidateURL, "application/json", strings.NewReader(string(jsonBody)))
	if err != nil {
		c.JSON(http.StatusBadGateway, gin.H{"error": "Failed to connect to SSO provider"})
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		fmt.Printf("SSO Validation Failed: Status=%d, Body=%s\n", resp.StatusCode, string(bodyBytes))
		c.JSON(resp.StatusCode, gin.H{"error": "SSO Validation Failed", "details": string(bodyBytes)})
		return
	}

	// 3. Parse Response
	var ssoResp SSOResponse
	bodyBytes, _ := io.ReadAll(resp.Body)
	if err := json.Unmarshal(bodyBytes, &ssoResp); err != nil {
		fmt.Printf("SSO Parse Error: %v, Body=%s\n", err, string(bodyBytes))
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse SSO response"})
		return
	}

	if !ssoResp.Valid {
		fmt.Printf("SSO Invalid Ticket: %+v\n", ssoResp)
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid SSO Ticket"})
		return
	}

	// 4. Find or Create User
	// Strict: Only use MappedKey
	if ssoResp.MappedKey == "" {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "SSO Login Failed: No mapped user key provided"})
		return
	}

	ssoUser := SSOUserStruct{
		Username: ssoResp.MappedKey,
		Name:     ssoResp.MappedKey,
		Email:    ssoResp.MappedEmail,
	}

	user, err := FindOrCreateUserFromSSO(ssoUser)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to process user: " + err.Error()})
		return
	}

	// 5. Generate Token
	token, err := utils.GenerateToken(*user)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
		return
	}

	user.Password = ""
	c.JSON(http.StatusOK, LoginResponse{
		Token: token,
		User:  *user,
	})
}
