package services

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"strings"
	"time"

	"trustcontact/internal/auth"
	"trustcontact/internal/config"
	"trustcontact/internal/mailer"
	"trustcontact/internal/models"
	"trustcontact/internal/repo"

	"gorm.io/gorm"
)

var (
	ErrEmailAlreadyExists    = errors.New("email already exists")
	ErrInvalidCredentials    = errors.New("invalid credentials")
	ErrEmailNotVerified      = errors.New("email not verified")
	ErrInvalidOrExpiredToken = errors.New("invalid or expired token")
	ErrInvalidLanguage       = errors.New("invalid language")
	ErrInvalidTheme          = errors.New("invalid theme")
)

var supportedLanguages = map[string]struct{}{
	"it":    {},
	"en":    {},
	"en_us": {},
	"de":    {},
	"fr":    {},
	"de_ch": {},
	"fr_ch": {},
}

type AuthService struct {
	cfg            *config.Config
	users          *repo.UserRepo
	verifyTokens   *repo.EmailVerificationTokenRepo
	resetTokens    *repo.PasswordResetTokenRepo
	mailer         mailer.Mailer
	templateRender *mailer.TemplateRenderer
	nowFn          func() time.Time
}

func NewAuthService(database *gorm.DB, cfg *config.Config) (*AuthService, error) {
	sender, err := mailer.NewMailer(cfg)
	if err != nil {
		return nil, err
	}

	return &AuthService{
		cfg:            cfg,
		users:          repo.NewUserRepo(database),
		verifyTokens:   repo.NewEmailVerificationTokenRepo(database),
		resetTokens:    repo.NewPasswordResetTokenRepo(database),
		mailer:         sender,
		templateRender: mailer.NewTemplateRenderer(""),
		nowFn: func() time.Time {
			return time.Now().UTC()
		},
	}, nil
}

func (s *AuthService) Signup(ctx context.Context, email, password string) error {
	email = normalizeEmail(email)
	if email == "" || strings.TrimSpace(password) == "" {
		return ErrInvalidCredentials
	}

	existing, err := s.users.FindByEmail(email)
	if err != nil {
		return err
	}
	if existing != nil {
		return ErrEmailAlreadyExists
	}

	passwordHash, err := auth.HashPassword(password)
	if err != nil {
		return err
	}

	user := &models.User{
		Email:         email,
		PasswordHash:  passwordHash,
		EmailVerified: false,
		Role:          models.RoleUser,
	}
	if err := s.users.Create(user); err != nil {
		return err
	}

	return s.issueVerifyEmail(ctx, user)
}

func (s *AuthService) Login(email, password string) (*models.User, error) {
	email = normalizeEmail(email)
	user, err := s.users.FindByEmail(email)
	if err != nil {
		return nil, err
	}
	if user == nil {
		return nil, ErrInvalidCredentials
	}

	ok, err := auth.ComparePassword(user.PasswordHash, password)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, ErrInvalidCredentials
	}

	if !user.EmailVerified {
		return nil, ErrEmailNotVerified
	}

	return user, nil
}

func (s *AuthService) VerifyEmail(token string) error {
	hash := auth.HashToken(token)
	record, err := s.verifyTokens.FindValidByHash(hash, s.nowFn())
	if err != nil {
		return err
	}
	if record == nil {
		return ErrInvalidOrExpiredToken
	}

	if err := s.users.SetEmailVerified(record.UserID, true); err != nil {
		return err
	}

	return s.verifyTokens.DeleteByID(record.ID)
}

func (s *AuthService) ForgotPassword(ctx context.Context, email string) error {
	email = normalizeEmail(email)
	if email == "" {
		return nil
	}

	user, err := s.users.FindByEmail(email)
	if err != nil {
		return err
	}
	if user == nil || !user.EmailVerified {
		return nil
	}

	plainToken, err := auth.NewToken()
	if err != nil {
		return err
	}
	if err := s.resetTokens.DeleteByUserID(user.ID); err != nil {
		return err
	}

	record := &models.PasswordResetToken{
		UserID:    user.ID,
		TokenHash: auth.HashToken(plainToken),
		ExpiresAt: auth.ResetTokenExpiresAt(s.nowFn()),
	}
	if err := s.resetTokens.Create(record); err != nil {
		return err
	}

	return s.sendResetEmail(ctx, user, plainToken)
}

func (s *AuthService) ResetPassword(token, newPassword string) error {
	if strings.TrimSpace(newPassword) == "" {
		return ErrInvalidCredentials
	}

	hash := auth.HashToken(token)
	record, err := s.resetTokens.FindValidByHash(hash, s.nowFn())
	if err != nil {
		return err
	}
	if record == nil {
		return ErrInvalidOrExpiredToken
	}

	passwordHash, err := auth.HashPassword(newPassword)
	if err != nil {
		return err
	}

	if err := s.users.UpdatePasswordHash(record.UserID, passwordHash); err != nil {
		return err
	}

	return s.resetTokens.DeleteByID(record.ID)
}

func (s *AuthService) UpdateUserLanguage(userID string, lang string) error {
	normalized := NormalizeLanguage(lang)
	if !IsSupportedLanguage(normalized) {
		return ErrInvalidLanguage
	}

	return s.users.UpsertLanguagePreference(userID, normalized)
}

func (s *AuthService) UpdateUserTheme(userID string, theme string) error {
	normalized := NormalizeTheme(theme)
	if normalized != "dark" && normalized != "light" {
		return ErrInvalidTheme
	}
	return s.users.UpsertDarkPreference(userID, normalized == "dark")
}

func (s *AuthService) issueVerifyEmail(ctx context.Context, user *models.User) error {
	plainToken, err := auth.NewToken()
	if err != nil {
		return err
	}
	if err := s.verifyTokens.DeleteByUserID(user.ID); err != nil {
		return err
	}

	record := &models.EmailVerificationToken{
		UserID:    user.ID,
		TokenHash: auth.HashToken(plainToken),
		ExpiresAt: auth.VerifyTokenExpiresAt(s.nowFn()),
	}
	if err := s.verifyTokens.Create(record); err != nil {
		return err
	}

	verifyURL := strings.TrimRight(s.cfg.BaseURL, "/") + "/verify-email?token=" + url.QueryEscape(plainToken)
	htmlBody, textBody, err := s.templateRender.RenderVerifyEmail(mailer.TemplateData{
		AppName:   s.cfg.AppName,
		BaseURL:   s.cfg.BaseURL,
		VerifyURL: verifyURL,
		UserEmail: user.Email,
	})
	if err != nil {
		return fmt.Errorf("render verify email: %w", err)
	}

	if err := s.mailer.Send(ctx, user.Email, "Verify your email", htmlBody, textBody); err != nil {
		return fmt.Errorf("send verify email: %w", err)
	}

	return nil
}

func (s *AuthService) sendResetEmail(ctx context.Context, user *models.User, plainToken string) error {
	resetURL := strings.TrimRight(s.cfg.BaseURL, "/") + "/reset-password?token=" + url.QueryEscape(plainToken)
	htmlBody, textBody, err := s.templateRender.RenderResetPassword(mailer.TemplateData{
		AppName:   s.cfg.AppName,
		BaseURL:   s.cfg.BaseURL,
		ResetURL:  resetURL,
		UserEmail: user.Email,
	})
	if err != nil {
		return fmt.Errorf("render reset email: %w", err)
	}

	if err := s.mailer.Send(ctx, user.Email, "Reset your password", htmlBody, textBody); err != nil {
		return fmt.Errorf("send reset email: %w", err)
	}

	return nil
}

func normalizeEmail(email string) string {
	return strings.ToLower(strings.TrimSpace(email))
}

func NormalizeLanguage(lang string) string {
	normalized := strings.ToLower(strings.TrimSpace(lang))
	return strings.ReplaceAll(normalized, "-", "_")
}

func IsSupportedLanguage(lang string) bool {
	_, ok := supportedLanguages[NormalizeLanguage(lang)]
	return ok
}

func NormalizeTheme(theme string) string {
	return strings.ToLower(strings.TrimSpace(theme))
}
