package controllers

import (
	cryptorand "crypto/rand"
	"encoding/hex"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"fotbal-club/internal/config"
	"fotbal-club/internal/models"
	"fotbal-club/pkg/email"
	"fotbal-club/pkg/logger"
	"fotbal-club/pkg/utils"

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

const (
	verificationCodeLength  = 6
	verificationCodeExpiry  = 10 * time.Minute
	maxVerificationAttempts = 3
)

type PasswordController struct {
	DB           *gorm.DB
	EmailService email.EmailService
}

// generateVerificationCode generates a random 6-digit verification code (crypto-secure)
func generateVerificationCode() string {
	const digits = "0123456789"
	b := make([]byte, verificationCodeLength)
	for i := 0; i < verificationCodeLength; i++ {
		// draw a random byte and map into 0-9 range without modulo bias concerns for small range
		var rb [1]byte
		if _, err := cryptorand.Read(rb[:]); err != nil {
			// fallback to time-based digit if crypto fails (extremely unlikely)
			b[i] = digits[int(time.Now().UnixNano())%10]
			continue
		}
		b[i] = digits[int(rb[0])%10]
	}
	return string(b)
}

// InitiatePasswordReset starts the password reset process by sending a verification code
func (pc *PasswordController) InitiatePasswordReset(c *gin.Context) {
	var req InitiatePasswordResetRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, PasswordResetResponse{
			Success: false,
			Message: "Neplatný formát požadavku: " + err.Error(),
		})
		return
	}

	emailAddr := strings.TrimSpace(strings.ToLower(req.Email))
	if emailAddr == "" || !strings.Contains(emailAddr, "@") {
		c.JSON(http.StatusBadRequest, PasswordResetResponse{
			Success: false,
			Message: "Neplatná e-mailová adresa",
		})
		return
	}

	// Check if email service is available
	if pc.EmailService == nil {
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "E-mailová služba není k dispozici. Kontaktujte prosím správce.",
		})
		return
	}

	// Check if user exists
	var user models.User
	if err := pc.DB.Where("LOWER(email) = LOWER(?)", emailAddr).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			// Return success to prevent email enumeration
			c.JSON(http.StatusOK, PasswordResetResponse{
				Success:  true,
				Message:  "Pokud účet s tímto e-mailem existuje, byl odeslán ověřovací kód.",
				NextStep: "verify_code",
			})
			return
		}
		// Other database error
		logger.Error("Database error when checking user: error=%v email=%s", err, emailAddr)
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "Došlo k chybě při ověřování e-mailu",
		})
		return
	}

	// Generate a secure random token
	token := make([]byte, 32)
	if _, err := cryptorand.Read(token); err != nil {
		logger.Error("Failed to generate random token: %v", err)
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "Nepodařilo se vygenerovat bezpečnostní token",
		})
		return
	}
	tokenStr := hex.EncodeToString(token)

	// Generate verification code
	verificationCode := generateVerificationCode()
	expiresAt := time.Now().Add(verificationCodeExpiry)

	// Create or update password reset record
	var pr models.PasswordReset
	err := pc.DB.Transaction(func(tx *gorm.DB) error {
		// Invalidate any existing reset tokens for this user
		if err := tx.Model(&models.PasswordReset{}).
			Where("user_id = ? AND used_at IS NULL", user.ID).
			Update("used_at", time.Now()).Error; err != nil {
			return err
		}

		// Create new reset record
		pr = models.PasswordReset{
			UserID:                  user.ID,
			Token:                   tokenStr,
			ExpiresAt:               time.Now().Add(time.Hour),
			VerificationCode:        verificationCode,
			VerificationCodeExpires: &expiresAt,
			VerificationAttempts:    0,
			IP:                      c.ClientIP(),
			UserAgent:               c.Request.UserAgent(),
		}

		return tx.Create(&pr).Error
	})

	if err != nil {
		logger.Error("Failed to create password reset record: %v", err)
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "Nepodařilo se vytvořit záznam pro obnovu hesla",
		})
		return
	}

	// Send verification code via email
	if seCode, ok := pc.EmailService.(interface{ SendPasswordResetCode(to, code string) error }); ok {
		if err := seCode.SendPasswordResetCode(user.Email, verificationCode); err != nil {
			// Fallback to legacy method if available
			if seLegacy, ok2 := pc.EmailService.(interface {
				SendPasswordReset(to, resetLink string, useOverride bool) error
			}); ok2 {
				if err2 := seLegacy.SendPasswordReset(user.Email, verificationCode, true); err2 != nil {
					logger.Error("Failed to send verification code (both methods): error=%v email=%s", err2, emailAddr)
					if !config.AppConfig.Debug {
						c.JSON(http.StatusInternalServerError, PasswordResetResponse{
							Success: false,
							Message: "Nepodařilo se odeslat ověřovací kód",
						})
						return
					}
				}
			} else {
				logger.Error("No available email method for sending verification code: email=%s error=%v", emailAddr, err)
				if !config.AppConfig.Debug {
					c.JSON(http.StatusInternalServerError, PasswordResetResponse{
						Success: false,
						Message: "Nepodařilo se odeslat ověřovací kód",
					})
					return
				}
			}
		}
	} else if seLegacy, ok := pc.EmailService.(interface {
		SendPasswordReset(to, resetLink string, useOverride bool) error
	}); ok {
		if err := seLegacy.SendPasswordReset(user.Email, verificationCode, true); err != nil {
			logger.Error("Failed to send verification code (legacy): error=%v email=%s", err, emailAddr)
			if !config.AppConfig.Debug {
				c.JSON(http.StatusInternalServerError, PasswordResetResponse{
					Success: false,
					Message: "Nepodařilo se odeslat ověřovací kód",
				})
				return
			}
		}
	}

	c.JSON(http.StatusOK, PasswordResetResponse{
		Success:      true,
		Message:      "Ověřovací kód byl odeslán na váš e-mail.",
		NextStep:     "verify_code",
		CodeRequired: true,
	})
}

// VerifyResetCode verifies the reset code and returns a reset token if valid
func (pc *PasswordController) VerifyResetCode(c *gin.Context) {
	var req VerifyResetCodeRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, PasswordResetResponse{
			Success: false,
			Message: "Neplatný formát požadavku: " + err.Error(),
		})
		return
	}

	emailAddr := strings.TrimSpace(strings.ToLower(req.Email))
	if emailAddr == "" || !strings.Contains(emailAddr, "@") {
		c.JSON(http.StatusBadRequest, PasswordResetResponse{
			Success: false,
			Message: "Neplatná e-mailová adresa",
		})
		return
	}

	// Find the user
	var user models.User
	if err := pc.DB.Where("LOWER(email) = LOWER(?)", emailAddr).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, PasswordResetResponse{
				Success: false,
				Message: "Uživatel s tímto e-mailem nebyl nalezen",
			})
			return
		}
		logger.Error("Database error when finding user: error=%v email=%s", err, emailAddr)
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "Došlo k chybě při ověřování kódu",
		})
		return
	}

	// Find the active reset record
	var pr models.PasswordReset
	now := time.Now()
	err := pc.DB.Transaction(func(tx *gorm.DB) error {
		// Find the most recent active reset record
		if err := tx.Where("user_id = ? AND used_at IS NULL AND expires_at > ?", user.ID, now).
			Order("created_at DESC").First(&pr).Error; err != nil {
			return err
		}

		// Check if verification code has expired
		if pr.VerificationCodeExpires == nil || pr.VerificationCodeExpires.Before(now) {
			return fmt.Errorf("verification code expired")
		}

		// Check if max attempts reached
		if pr.VerificationAttempts >= maxVerificationAttempts {
			return fmt.Errorf("max verification attempts reached")
		}

		// Check if code matches
		if pr.VerificationCode != req.Code {
			// Increment attempt counter
			if err := tx.Model(&pr).
				Update("verification_attempts", pr.VerificationAttempts+1).Error; err != nil {
				return err
			}
			return fmt.Errorf("invalid verification code")
		}

		// Code is valid; do not expire it here. Allow completion step to validate within original expiry window.
		return nil
	})

	if err != nil {
		if err.Error() == "record not found" {
			c.JSON(http.StatusNotFound, PasswordResetResponse{
				Success: false,
				Message: "Neplatný nebo expirovaný odkaz pro obnovení hesla",
			})
			return
		} else if err.Error() == "verification code expired" {
			c.JSON(http.StatusBadRequest, PasswordResetResponse{
				Success: false,
				Message: "Platnost ověřovacího kódu vypršela. Požádejte o nový kód.",
			})
			return
		} else if err.Error() == "max verification attempts reached" {
			c.JSON(http.StatusTooManyRequests, PasswordResetResponse{
				Success: false,
				Message: "Překročen maximální počet pokusů. Požádejte o nový kód.",
			})
			return
		} else if err.Error() == "invalid verification code" {
			attemptsLeft := maxVerificationAttempts - pr.VerificationAttempts - 1
			message := fmt.Sprintf("Neplatný ověřovací kód. Zbývající počet pokusů: %d", attemptsLeft)
			c.JSON(http.StatusBadRequest, PasswordResetResponse{
				Success: false,
				Message: message,
			})
			return
		}

		logger.Error("Error verifying reset code: error=%v email=%s", err, emailAddr)
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "Došlo k chybě při ověřování kódu",
		})
		return
	}

	// Return success with the reset token
	c.JSON(http.StatusOK, PasswordResetResponse{
		Success:  true,
		Message:  "Kód byl úspěšně ověřen. Můžete nastavit nové heslo.",
		NextStep: "reset_password",
	})
}

// CompletePasswordReset verifies the reset code and updates the password
func (pc *PasswordController) CompletePasswordReset(c *gin.Context) {
	var req CompletePasswordResetRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, PasswordResetResponse{
			Success: false,
			Message: "Neplatný formát požadavku: " + err.Error(),
		})
		return
	}

	emailAddr := strings.TrimSpace(strings.ToLower(req.Email))
	if emailAddr == "" || !strings.Contains(emailAddr, "@") {
		c.JSON(http.StatusBadRequest, PasswordResetResponse{
			Success: false,
			Message: "Neplatná e-mailová adresa",
		})
		return
	}

	// Find the user
	var user models.User
	if err := pc.DB.Where("LOWER(email) = LOWER(?)", emailAddr).First(&user).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, PasswordResetResponse{
				Success: false,
				Message: "Uživatel s tímto e-mailem nebyl nalezen",
			})
			return
		}
		logger.Error("Database error when finding user: error=%v email=%s", err, emailAddr)
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "Došlo k chybě při obnovování hesla",
		})
		return
	}

	// Verify the reset code and get the reset record
	var pr models.PasswordReset
	now := time.Now()
	err := pc.DB.Transaction(func(tx *gorm.DB) error {
		// Find the active reset record
		if err := tx.Where("user_id = ? AND used_at IS NULL AND expires_at > ?", user.ID, now).
			Order("created_at DESC").First(&pr).Error; err != nil {
			return err
		}

		// Check if verification code matches and is not expired
		if pr.VerificationCode != req.Code ||
			pr.VerificationCodeExpires == nil ||
			pr.VerificationCodeExpires.Before(now) {
			return fmt.Errorf("invalid or expired verification code")
		}

		// Update user's password
		hashedPassword, err := utils.HashPassword(req.NewPassword)
		if err != nil {
			return fmt.Errorf("failed to hash password: %w", err)
		}

		if err := tx.Model(&user).Update("password", hashedPassword).Error; err != nil {
			return fmt.Errorf("failed to update password: %w", err)
		}

		// Mark the reset record as used
		now := time.Now()
		pr.UsedAt = &now
		return tx.Save(&pr).Error
	})

	if err != nil {
		if err.Error() == "record not found" || err.Error() == "invalid or expired verification code" {
			c.JSON(http.StatusBadRequest, PasswordResetResponse{
				Success: false,
				Message: "Neplatný nebo expirovaný ověřovací kód. Zkuste to znovu.",
			})
			return
		}

		logger.Error("Error completing password reset: error=%v email=%s", err, emailAddr)
		c.JSON(http.StatusInternalServerError, PasswordResetResponse{
			Success: false,
			Message: "Došlo k chybě při obnovování hesla",
		})
		return
	}

	c.JSON(http.StatusOK, PasswordResetResponse{
		Success:  true,
		Message:  "Vaše heslo bylo úspěšně změněno. Nyní se můžete přihlásit.",
		NextStep: "login",
	})
}

// AdminSendResetByID sends a reset email for a specific user ID using override SMTP
func (pc *PasswordController) AdminSendResetByID(c *gin.Context) {
	idStr := c.Param("id")
	id, err := strconv.Atoi(idStr)
	if err != nil || id <= 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	var user models.User
	if err := pc.DB.First(&user, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
		}
		return
	}

	// Check if email service is available
	if pc.EmailService == nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{
			"error": "Email service not configured",
			"message": "SMTP settings must be configured in the admin panel or via environment variables (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM) before sending password reset emails.",
		})
		return
	}

	var b [16]byte
	_, _ = cryptorand.Read(b[:])
	token := hex.EncodeToString(b[:])
	pr := &models.PasswordReset{
		UserID:    user.ID,
		Token:     token,
		ExpiresAt: time.Now().Add(1 * time.Hour),
		IP:        c.ClientIP(),
		UserAgent: c.Request.UserAgent(),
	}
	if err := pc.DB.Create(pr).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create reset record"})
		return
	}
	base := strings.TrimSuffix(config.AppConfig.FrontendBaseURL, "/")
	if base == "" {
		base = "http://localhost:3000"
	}
	link := base + "/reset-password?token=" + token
	if se, ok := pc.EmailService.(interface {
		SendPasswordReset(to, link string, useOverride bool) error
	}); ok {
		if err := se.SendPasswordReset(user.Email, link, true); err != nil {
			logger.Error("Failed to send password reset email: error=%v user_id=%d", err, user.ID)
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": "failed to send reset email",
				"message": "SMTP configuration may be invalid or incomplete. Check SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_FROM in .env file or configure in admin settings.",
				"details": err.Error(),
			})
			return
		}
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Email service does not support password reset"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"success": true})
}

func NewPasswordController(db *gorm.DB, es email.EmailService) *PasswordController {
	return &PasswordController{DB: db, EmailService: es}
}

type forgotPasswordRequest struct {
	Email string `json:"email" binding:"required"`
}

// ForgotPassword creates a reset token and sends email
func (pc *PasswordController) ForgotPassword(c *gin.Context) {
	var req forgotPasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	emailAddr := strings.TrimSpace(strings.ToLower(req.Email))
	if emailAddr == "" || !strings.Contains(emailAddr, "@") {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid email"})
		return
	}

	// Lookup user; don't reveal whether exists
	var user models.User
	if err := pc.DB.Where("LOWER(email) = LOWER(?)", emailAddr).First(&user).Error; err != nil {
		// Respond success to avoid user enumeration
		c.JSON(http.StatusOK, gin.H{"success": true})
		return
	}

	// Create token
	var b [16]byte
	_, _ = cryptorand.Read(b[:])
	token := hex.EncodeToString(b[:])
	pr := &models.PasswordReset{
		UserID:    user.ID,
		Token:     token,
		ExpiresAt: time.Now().Add(1 * time.Hour),
		IP:        c.ClientIP(),
		UserAgent: c.Request.UserAgent(),
	}
	if err := pc.DB.Create(pr).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create reset"})
		return
	}

	// Build reset link
	base := strings.TrimSuffix(config.AppConfig.FrontendBaseURL, "/")
	link := base + "/reset-password?token=" + token

	// Send email via default SMTP
	if se, ok := pc.EmailService.(interface {
		SendPasswordReset(to, link string, useOverride bool) error
	}); ok {
		if err := se.SendPasswordReset(user.Email, link, false); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to send reset email"})
			return
		}
	}

	c.JSON(http.StatusOK, gin.H{"success": true})
}

type resetPasswordRequest struct {
	Token       string `json:"token" binding:"required"`
	NewPassword string `json:"new_password" binding:"required,min=8"`
}

// ResetPassword consumes a token and sets a new password
func (pc *PasswordController) ResetPassword(c *gin.Context) {
	var req resetPasswordRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var pr models.PasswordReset
	if err := pc.DB.Where("token = ?", req.Token).First(&pr).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token"})
		return
	}
	if pr.UsedAt != nil || time.Now().After(pr.ExpiresAt) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "token expired or used"})
		return
	}
	var user models.User
	if err := pc.DB.First(&user, pr.UserID).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "user not found"})
		return
	}

	hashed, err := utils.HashPassword(req.NewPassword)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash"})
		return
	}
	user.Password = hashed
	if err := pc.DB.Save(&user).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save"})
		return
	}
	now := time.Now()
	pr.UsedAt = &now
	_ = pc.DB.Save(&pr).Error

	c.JSON(http.StatusOK, gin.H{"success": true})
}

type adminSendResetRequest struct {
	Email string `json:"email" binding:"required"`
}

// AdminSendReset sends a reset email using special SMTP when key matches
func (pc *PasswordController) AdminSendReset(c *gin.Context) {
	var req adminSendResetRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	emailAddr := strings.TrimSpace(strings.ToLower(req.Email))
	var user models.User
	if err := pc.DB.Where("LOWER(email) = LOWER(?)", emailAddr).First(&user).Error; err != nil {
		// respond ok regardless
		c.JSON(http.StatusOK, gin.H{"success": true})
		return
	}
	// Create token always fresh
	var b [16]byte
	_, _ = cryptorand.Read(b[:])
	token := hex.EncodeToString(b[:])
	pr := &models.PasswordReset{
		UserID:    user.ID,
		Token:     token,
		ExpiresAt: time.Now().Add(1 * time.Hour),
		IP:        c.ClientIP(),
		UserAgent: c.Request.UserAgent(),
	}
	if err := pc.DB.Create(pr).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed"})
		return
	}
	base := strings.TrimSuffix(config.AppConfig.FrontendBaseURL, "/")
	link := base + "/reset-password?token=" + token
	if se, ok := pc.EmailService.(interface {
		SendPasswordReset(to, link string, useOverride bool) error
	}); ok {
		if err := se.SendPasswordReset(user.Email, link, true); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to send reset email"})
			return
		}
	}
	c.JSON(http.StatusOK, gin.H{"success": true})
}
