package services

import (
	"bytes"
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/base64"
	"fmt"
	"html/template"
	"net/smtp"
	"os"
	"time"

	"veza-backend-api/internal/database"

	"go.uber.org/zap"
)

// EmailService handles email operations
type EmailService struct {
	db        *database.Database
	logger    *zap.Logger
	smtpHost  string
	smtpPort  string
	smtpUser  string
	smtpPass  string
	fromEmail string
	fromName  string
}

// NewEmailService creates a new email service
func NewEmailService(db *database.Database, logger *zap.Logger) *EmailService {
	return &EmailService{
		db:        db,
		logger:    logger,
		smtpHost:  os.Getenv("SMTP_HOST"),
		smtpPort:  os.Getenv("SMTP_PORT"),
		smtpUser:  os.Getenv("SMTP_USER"),
		smtpPass:  os.Getenv("SMTP_PASSWORD"),
		fromEmail: os.Getenv("FROM_EMAIL"),
		fromName:  os.Getenv("FROM_NAME"),
	}
}

// EmailVerificationToken represents an email verification token
type EmailVerificationToken struct {
	ID        int64     `db:"id"`
	UserID    int64     `db:"user_id"`
	Token     string    `db:"token"`
	ExpiresAt time.Time `db:"expires_at"`
	Used      bool      `db:"used"`
	CreatedAt time.Time `db:"created_at"`
}

// SendVerificationEmail sends a verification email to the user
// T0184: Accepte email et token (le token est généré et stocké par EmailVerificationService)
func (es *EmailService) SendVerificationEmail(email, token string) error {
	// T0184: Étape 3 - Générer URL de vérification avec token
	baseURL := os.Getenv("FRONTEND_URL")
	if baseURL == "" {
		baseURL = "http://localhost:5173"
	}
	verifyURL := fmt.Sprintf("%s/verify-email?token=%s", baseURL, token)

	// T0184: Étape 4 - Construire email HTML avec lien
	subject := "Verify your Veza account"
	body := es.buildVerificationEmailHTML(verifyURL)

	// T0184: Étape 5 - Envoyer email via SMTP (gestion erreurs sans faire échouer registration)
	err := es.sendEmail(email, subject, body)
	if err != nil {
		return fmt.Errorf("failed to send verification email: %w", err)
	}

	es.logger.Info("Verification email sent",
		zap.String("email", email),
	)

	return nil
}

// SendVerificationEmailWithUserID sends a verification email to the user (legacy method for backward compatibility)
// This method generates and stores the token itself
func (es *EmailService) SendVerificationEmailWithUserID(userID int64, email string) error {
	// Generate verification token
	token, err := es.generateVerificationToken()
	if err != nil {
		return fmt.Errorf("failed to generate verification token: %w", err)
	}

	// Store token in database
	err = es.storeVerificationToken(userID, token)
	if err != nil {
		return fmt.Errorf("failed to store verification token: %w", err)
	}

	// Use the new method to send the email
	return es.SendVerificationEmail(email, token)
}

// VerifyEmailToken verifies an email verification token
func (es *EmailService) VerifyEmailToken(token string) (int64, error) {
	var vt EmailVerificationToken

	ctx := context.Background()
	err := es.db.QueryRowContext(ctx, `
		SELECT id, user_id, token, expires_at, used, created_at
		FROM email_verification_tokens
		WHERE token = $1 AND used = FALSE
	`, token).Scan(
		&vt.ID,
		&vt.UserID,
		&vt.Token,
		&vt.ExpiresAt,
		&vt.Used,
		&vt.CreatedAt,
	)

	if err != nil {
		if err == sql.ErrNoRows {
			return 0, fmt.Errorf("invalid or expired verification token")
		}
		return 0, fmt.Errorf("failed to verify token: %w", err)
	}

	// Check if token has expired
	if time.Now().After(vt.ExpiresAt) {
		return 0, fmt.Errorf("verification token has expired")
	}

	// Mark token as used
	_, err = es.db.ExecContext(ctx, `
		UPDATE email_verification_tokens
		SET used = TRUE
		WHERE id = $1
	`, vt.ID)
	if err != nil {
		return 0, fmt.Errorf("failed to mark token as used: %w", err)
	}

	// Update user's email verification status
	_, err = es.db.ExecContext(ctx, `
		UPDATE users
		SET email_verified = TRUE, email_verified_at = NOW()
		WHERE id = $1
	`, vt.UserID)
	if err != nil {
		return 0, fmt.Errorf("failed to update user email verification: %w", err)
	}

	es.logger.Info("Email verified",
		zap.Int64("user_id", vt.UserID),
	)

	return vt.UserID, nil
}

// ResendVerificationEmail resends a verification email
func (es *EmailService) ResendVerificationEmail(userID int64, email string) error {
	ctx := context.Background()

	// Check if already verified
	var verified bool
	err := es.db.QueryRowContext(ctx, `
		SELECT email_verified
		FROM users
		WHERE id = $1
	`, userID).Scan(&verified)

	if err != nil {
		return fmt.Errorf("failed to check verification status: %w", err)
	}

	if verified {
		return fmt.Errorf("email already verified")
	}

	// Invalidate old tokens for this user
	_, err = es.db.ExecContext(ctx, `
		UPDATE email_verification_tokens
		SET used = TRUE
		WHERE user_id = $1 AND used = FALSE
	`, userID)
	if err != nil {
		es.logger.Warn("Failed to invalidate old tokens",
			zap.Error(err),
			zap.Int64("user_id", userID),
		)
	}

	// Send new verification email (use legacy method that generates token)
	return es.SendVerificationEmailWithUserID(userID, email)
}

// generateVerificationToken generates a secure random token
func (es *EmailService) generateVerificationToken() (string, error) {
	bytes := make([]byte, 32)
	_, err := rand.Read(bytes)
	if err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(bytes), nil
}

// storeVerificationToken stores a verification token in the database
func (es *EmailService) storeVerificationToken(userID int64, token string) error {
	ctx := context.Background()
	expiresAt := time.Now().Add(24 * time.Hour) // Token expires in 24 hours

	_, err := es.db.ExecContext(ctx, `
		INSERT INTO email_verification_tokens (user_id, token, expires_at, used)
		VALUES ($1, $2, $3, FALSE)
	`, userID, token, expiresAt)

	return err
}

// sendEmail sends an email using SMTP
func (es *EmailService) sendEmail(to, subject, body string) error {
	// If no SMTP configured, just log (for development)
	if es.smtpHost == "" {
		es.logger.Info("Email not configured, logging instead",
			zap.String("to", to),
			zap.String("subject", subject),
		)
		return nil
	}

	// SMTP auth
	auth := smtp.PlainAuth("", es.smtpUser, es.smtpPass, es.smtpHost)

	// Email headers
	msg := []byte(fmt.Sprintf("From: %s <%s>\r\n"+
		"To: %s\r\n"+
		"Subject: %s\r\n"+
		"MIME-Version: 1.0\r\n"+
		"Content-Type: text/html; charset=UTF-8\r\n"+
		"\r\n"+
		"%s", es.fromName, es.fromEmail, to, subject, body))

	// Send email
	addr := fmt.Sprintf("%s:%s", es.smtpHost, es.smtpPort)
	err := smtp.SendMail(addr, auth, es.fromEmail, []string{to}, msg)
	if err != nil {
		return fmt.Errorf("failed to send email: %w", err)
	}

	return nil
}

// buildVerificationEmailHTML builds the HTML email template
// T0184: Construit l'email HTML avec lien de vérification
func (es *EmailService) buildVerificationEmailHTML(url string) string {
	tmpl := `
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Verify your Veza account</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
    <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
        <h1 style="color: #4CAF50;">Welcome to Veza!</h1>
        <p>Thank you for signing up. Please verify your email address to complete your registration.</p>
        <div style="text-align: center; margin: 30px 0;">
            <a href="{{.VerifyURL}}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
                Verify Email Address
            </a>
        </div>
        <p>Or copy and paste this link into your browser:</p>
        <p style="word-break: break-all; color: #666;">{{.VerifyURL}}</p>
        <p style="margin-top: 30px; color: #666; font-size: 12px;">
            This link will expire in 24 hours.
        </p>
    </div>
</body>
</html>
`

	t, err := template.New("verification").Parse(tmpl)
	if err != nil {
		return fmt.Sprintf("Click here to verify your email: %s", url)
	}

	var buf bytes.Buffer
	err = t.Execute(&buf, map[string]string{
		"VerifyURL": url,
	})
	if err != nil {
		return fmt.Sprintf("Click here to verify your email: %s", url)
	}

	return buf.String()
}

// SendPasswordResetEmail sends a password reset email
func (es *EmailService) SendPasswordResetEmail(userID int64, email string, token string) error {
	// Build reset URL
	baseURL := os.Getenv("FRONTEND_URL")
	if baseURL == "" {
		baseURL = "http://localhost:5173"
	}
	resetURL := fmt.Sprintf("%s/reset-password?token=%s", baseURL, token)

	// Prepare email content
	subject := "Reset your Veza password"
	body := es.buildPasswordResetEmail(resetURL)

	// Send email
	err := es.sendEmail(email, subject, body)
	if err != nil {
		return fmt.Errorf("failed to send password reset email: %w", err)
	}

	es.logger.Info("Password reset email sent",
		zap.Int64("user_id", userID),
		zap.String("email", email),
	)

	return nil
}

// buildPasswordResetEmail builds the HTML password reset email template
func (es *EmailService) buildPasswordResetEmail(url string) string {
	tmpl := `
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Reset your Veza password</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
    <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
        <h1 style="color: #4CAF50;">Reset your password</h1>
        <p>You requested to reset your Veza account password. Click the button below to continue.</p>
        <div style="text-align: center; margin: 30px 0;">
            <a href="{{.ResetURL}}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
                Reset Password
            </a>
        </div>
        <p>Or copy and paste this link into your browser:</p>
        <p style="word-break: break-all; color: #666;">{{.ResetURL}}</p>
        <p style="margin-top: 30px; color: #666; font-size: 12px;">
            This link will expire in 1 hour. If you didn't request this, please ignore this email.
        </p>
    </div>
</body>
</html>
`

	t, err := template.New("password_reset").Parse(tmpl)
	if err != nil {
		return fmt.Sprintf("Click here to reset your password: %s", url)
	}

	var buf bytes.Buffer
	err = t.Execute(&buf, map[string]string{
		"ResetURL": url,
	})
	if err != nil {
		return fmt.Sprintf("Click here to reset your password: %s", url)
	}

	return buf.String()
}
