package users

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v3"
	"github.com/google/uuid"
	"gorm.io/gorm"

	"server/internal/db"
	"server/internal/mail"
	"server/internal/responses"
	"server/internal/systemUtils"
	"server/internal/tokens"

	"server/internal/validation"
)

type UserController struct {
	TockenService *tokens.TockenService
}

func NewUserController(tockenService *tokens.TockenService) *UserController {
	return &UserController{
		TockenService: tockenService,
	}
}

// Typescript: interface
type UpdateUserRequest struct {
	Name        string           `json:"name" validate:"required,min=1,max=255"`
	Email       string           `json:"email" validate:"required,email"`
	Password    string           `json:"password" validate:"omitempty,min=8,max=128"`
	Roles       UserRoles        `json:"roles"`
	Status      UserStatus       `json:"status"`
	Types       UserTypes        `json:"types"`
	Avatar      *string          `json:"avatar"`
	Details     *UserDetails     `json:"details"`
	Preferences *UserPreferences `json:"preferences"`
}

// GetUser returns a single user by UUID.
func (uc *UserController) GetUser(c fiber.Ctx) error {
	user, err := loadUserByUUID(c)
	if err != nil {
		return err
	}
	return c.JSON(responses.Success(ToUserProfile(user)))
}

// CreateUser creates a user together with optional details and preferences.
func (uc *UserController) CreateUser(c fiber.Ctx) error {
	var req UserCreateInput
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validation.ValidateStruct(&req); err != nil {
		return err
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	var existing User
	if err := db.Where("email = ?", req.Email).First(&existing).Error; err == nil {
		return fiber.NewError(fiber.StatusConflict, "user already exists")
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
	}

	hashedPassword, err := systemUtils.HashPassword(req.Password)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password")
	}

	now := time.Now().UTC()
	user := User{
		Email:    req.Email,
		Name:     req.Name,
		Password: hashedPassword,
		Roles: func() UserRoles {
			if len(req.Roles) == 0 {
				return UserRoles{"user"}
			}
			return req.Roles
		}(),
		Status: func() UserStatus {
			if req.Status == "" {
				return UserStatusPending
			}
			return req.Status
		}(),
		Types: func() UserTypes {
			if len(req.Types) == 0 {
				return UserTypes{"internal"}
			}
			return req.Types
		}(),
		Avatar:      req.Avatar,
		UUID:        uuid.NewString(),
		Details:     req.Details,
		Preferences: req.Preferences,
		CreatedAt:   now,
		UpdatedAt:   now,
	}

	if err := db.Create(&user).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to create user")
	}

	if err := db.Preload("Details").Preload("Preferences").First(&user, user.ID).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to reload user")
	}

	return c.Status(fiber.StatusCreated).JSON(responses.Success(ToUserProfile(&user)))
}

// UpdateUser replaces user fields and synchronizes details/preferences.
func (uc *UserController) UpdateUser(c fiber.Ctx) error {
	var req UpdateUserRequest
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validation.ValidateStruct(&req); err != nil {
		return err
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	user, err := loadUserByUUID(c)
	if err != nil {
		return err
	}

	if req.Email != user.Email {
		var existing User
		if err := db.Select("id").Where("email = ?", req.Email).First(&existing).Error; err == nil && existing.ID != user.ID {
			return fiber.NewError(fiber.StatusConflict, "user already exists")
		} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
		}
	}

	now := time.Now().UTC()
	user.Name = req.Name
	user.Email = req.Email
	user.Avatar = req.Avatar
	user.UpdatedAt = now
	if req.Status != "" {
		user.Status = req.Status
	}
	if len(req.Roles) > 0 {
		user.Roles = req.Roles
	}
	if len(req.Types) > 0 {
		user.Types = req.Types
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Save(user).Error; err != nil {
			return err
		}
		if err := syncUserDetails(tx, user.ID, req.Details); err != nil {
			return err
		}
		if err := syncUserPreferences(tx, user.ID, req.Preferences); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to update user")
	}

	if err := db.Preload("Details").Preload("Preferences").First(user, user.ID).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to reload user")
	}

	return c.JSON(responses.Success(ToUserProfile(user)))
}

// DeleteUser removes a user and linked details/preferences through cascading delete rules.
func (uc *UserController) DeleteUser(c fiber.Ctx) error {
	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	user, err := loadUserByID(c)
	if err != nil {
		return err
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("user_id = ?", user.ID).Delete(&UserDetails{}).Error; err != nil {
			return err
		}
		if err := tx.Where("user_id = ?", user.ID).Delete(&UserPreferences{}).Error; err != nil {
			return err
		}
		return tx.Delete(user).Error
	}); err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to delete user")
	}

	return c.JSON(responses.Success(responses.SimpleResponse{Message: "user deleted"}))
}

// Login authenticates a user and issues an access/refresh token pair.
func (uc *UserController) Login(c fiber.Ctx) error {
	var req LoginRequest
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validation.ValidateStruct(&req); err != nil {
		return err
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	var user User
	if err := db.Where("email = ?", req.Username).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusUnauthorized, "invalid credentials")
		}
		return fiber.NewError(fiber.StatusInternalServerError, "failed to fetch user")
	}
	match, err := systemUtils.VerifyPassword(user.Password, req.Password)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to verify credentials")
	}
	if !match {
		return fiber.NewError(fiber.StatusUnauthorized, "invalid credentials")
	}

	token, err := uc.TockenService.GenerateTokenPair(user.Email)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to issue token")
	}

	userID := user.ID
	now := time.Now().UTC()
	if err := db.Where("expires_at < ?", now).Delete(&Session{}).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to purge expired sessions")
	}

	session := Session{
		UserID:           &userID,
		Username:         user.Email,
		AccessTokenHash:  tokens.HashToken(token.AccessToken),
		RefreshTokenHash: tokens.HashToken(token.RefreshToken),
		ExpiresAt:        now.Add(uc.TockenService.RefreshExpiry()),
		IPAddress:        c.IP(),
		UserAgent:        c.Get("User-Agent"),
	}

	if err := db.Create(&session).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to record session")
	}

	c.Response().Header.Set("Auth-Token", token.AccessToken)

	return c.JSON(responses.Success(token))
}

// Register creates a new user with optional roles/types/preferences.
func (uc *UserController) Register(c fiber.Ctx) error {
	var req UserCreateInput
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validation.ValidateStruct(&req); err != nil {
		return err
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	var existing User
	if err := db.Where("email = ?", req.Email).First(&existing).Error; err == nil {
		return fiber.NewError(fiber.StatusConflict, "user already exists")
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to check user")
	}

	now := time.Now().UTC()
	hashedPassword, err := systemUtils.HashPassword(req.Password)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password")
	}
	user := User{
		Email:    req.Email,
		Name:     req.Name,
		Password: hashedPassword,
		Roles: func() UserRoles {
			if len(req.Roles) == 0 {
				return UserRoles{"user"}
			}
			return req.Roles
		}(),
		Status: func() UserStatus {
			if req.Status == "" {
				return UserStatusPending
			}
			return req.Status
		}(),
		Types: func() UserTypes {
			if len(req.Types) == 0 {
				return UserTypes{"internal"}
			}
			return req.Types
		}(),
		Avatar:  req.Avatar,
		UUID:    uuid.NewString(),
		Details: req.Details,
		Preferences: func() *UserPreferences {
			if req.Preferences == nil {
				return nil
			}
			return &UserPreferences{
				UseIdle:          req.Preferences.UseIdle,
				IdleTimeout:      req.Preferences.IdleTimeout,
				UseIdlePassword:  req.Preferences.UseIdlePassword,
				IdlePin:          req.Preferences.IdlePin,
				UseDirectLogin:   req.Preferences.UseDirectLogin,
				UseQuadcodeLogin: req.Preferences.UseQuadcodeLogin,
				SendNoticesMail:  req.Preferences.SendNoticesMail,
				Language:         req.Preferences.Language,
			}
		}(),
		CreatedAt: now,
		UpdatedAt: now,
	}

	if err := db.Create(&user).Error; err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to create user")
	}

	mailService, err := mail.New()
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to initialize mail service")
	}

	if err := mailService.Send(c, mail.Message{
		To:       user.Email,
		Subject:  fmt.Sprintf("[%s] Registrazione completata", mailService.AppName()),
		Template: "registration",
		TemplateData: mail.TemplateData{
			AppName:   mailService.AppName(),
			UserName:  user.Name,
			UserEmail: user.Email,
		},
	}); err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to send registration email")
	}

	return c.Status(fiber.StatusCreated).JSON(responses.Success(&user))
}

func (uc *UserController) ForgotPassword(c fiber.Ctx) error {
	var req ForgotPasswordRequest
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validation.ValidateStruct(&req); err != nil {
		return err
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	var user User
	if err := db.Where("email = ?", req.Email).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return c.JSON(responses.Success(fiber.Map{"sent": true}))
		}
		return fiber.NewError(fiber.StatusInternalServerError, "failed to load user")
	}

	if user.Status == UserStatusDisabled {
		return c.JSON(responses.Success(fiber.Map{"sent": true}))
	}

	resetToken, err := uc.TockenService.GenerateSecureToken()
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to generate reset token")
	}

	now := time.Now().UTC()
	record := PasswordResetToken{
		UserID:    user.ID,
		TokenHash: tokens.HashToken(resetToken),
		ExpiresAt: now.Add(30 * time.Minute),
		CreatedAt: now,
		UpdatedAt: now,
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("user_id = ? OR expires_at < ? OR used_at IS NOT NULL", user.ID, now).
			Delete(&PasswordResetToken{}).Error; err != nil {
			return err
		}
		return tx.Create(&record).Error
	}); err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to store reset token")
	}

	mailService, err := mail.New()
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to initialize mail service")
	}
	if err := mailService.Send(c, mail.Message{
		To:       user.Email,
		Subject:  fmt.Sprintf("[%s] Recupero password", mailService.AppName()),
		Template: "password_reset",
		TemplateData: mail.TemplateData{
			AppName:    mailService.AppName(),
			UserName:   user.Name,
			UserEmail:  user.Email,
			ResetToken: resetToken,
			ResetURL:   mailService.ResetLink(resetToken),
		},
	}); err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to send reset email")
	}

	return c.JSON(responses.Success(responses.SimpleResponse{Message: "password reset email sent"}))
}

func (uc *UserController) ResetPassword(c fiber.Ctx) error {
	var req ResetPasswordRequest
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if err := validation.ValidateStruct(&req); err != nil {
		return err
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	hashedPassword, err := systemUtils.HashPassword(req.Password)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to secure password")
	}

	now := time.Now().UTC()
	tokenHash := tokens.HashToken(req.Token)
	if err := db.Transaction(func(tx *gorm.DB) error {
		var resetToken PasswordResetToken
		if err := tx.Where("token_hash = ?", tokenHash).First(&resetToken).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
			}
			return err
		}
		if resetToken.UsedAt != nil || now.After(resetToken.ExpiresAt) {
			return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
		}

		if err := tx.Model(&User{}).Where("id = ?", resetToken.UserID).Updates(map[string]any{
			"password":   hashedPassword,
			"updated_at": now,
		}).Error; err != nil {
			return err
		}
		if err := tx.Model(&resetToken).Updates(map[string]any{
			"used_at":    now,
			"updated_at": now,
		}).Error; err != nil {
			return err
		}
		if err := tx.Where("user_id = ?", resetToken.UserID).Delete(&Session{}).Error; err != nil {
			return err
		}
		if err := tx.Where("user_id = ? AND id <> ?", resetToken.UserID, resetToken.ID).Delete(&PasswordResetToken{}).Error; err != nil {
			return err
		}
		return nil
	}); err != nil {
		var fiberErr *fiber.Error
		if errors.As(err, &fiberErr) {
			return fiberErr
		}
		return fiber.NewError(fiber.StatusInternalServerError, "failed to reset password")
	}

	return c.JSON(responses.Success(responses.SimpleResponse{Message: "password reset successful"}))
}

func (uc *UserController) ValidToken(c fiber.Ctx) error {
	raw := strings.TrimSpace(string(c.Body()))
	if raw == "" {
		return fiber.NewError(fiber.StatusBadRequest, "token is required")
	}

	token := raw
	if strings.HasPrefix(raw, "\"") && strings.HasSuffix(raw, "\"") {
		if err := json.Unmarshal([]byte(raw), &token); err != nil {
			return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
		}
	}
	token = strings.TrimSpace(token)
	if token == "" {
		return fiber.NewError(fiber.StatusBadRequest, "token is required")
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	now := time.Now().UTC()
	tokenHash := tokens.HashToken(token)
	var resetToken PasswordResetToken
	if err := db.Where("token_hash = ?", tokenHash).First(&resetToken).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
		}
		return fiber.NewError(fiber.StatusInternalServerError, "failed to validate reset token")
	}

	if resetToken.UsedAt != nil || now.After(resetToken.ExpiresAt) {
		return fiber.NewError(fiber.StatusBadRequest, "invalid or expired reset token")
	}

	return c.JSON(responses.Success(responses.SimpleResponse{Message: "valid reset token"}))
}

func loadUserByID(c fiber.Ctx) (*User, error) {
	id, err := strconv.Atoi(c.Params("id"))
	if err != nil || id <= 0 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "invalid user id")
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return nil, err
	}

	var user User
	if err := db.Preload("Details").Preload("Preferences").First(&user, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "user not found")
		}
		return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to load user")
	}

	return &user, nil
}

func loadUserByUUID(c fiber.Ctx) (*User, error) {
	uuid := c.Params("uuid")
	if uuid == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "invalid user uuid")
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return nil, err
	}

	var user User
	if err := db.Preload("Details").Preload("Preferences").First(&user, "uuid = ?", uuid).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "user not found")
		}
		return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to load user")
	}

	return &user, nil
}

func syncUserDetails(tx *gorm.DB, userID int, input *UserDetails) error {
	if input == nil {
		return tx.Where("user_id = ?", userID).Delete(&UserDetails{}).Error
	}

	var details UserDetails
	if err := tx.Where("user_id = ?", userID).First(&details).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			details = UserDetails{UserID: userID}
		} else {
			return err
		}
	}

	details.Title = input.Title
	details.FirstName = input.FirstName
	details.LastName = input.LastName
	details.Address = input.Address
	details.City = input.City
	details.ZipCode = input.ZipCode
	details.Country = input.Country
	details.Phone = input.Phone

	if details.ID == 0 {
		return tx.Create(&details).Error
	}
	return tx.Save(&details).Error
}

func syncUserPreferences(tx *gorm.DB, userID int, input *UserPreferences) error {
	if input == nil {
		return tx.Where("user_id = ?", userID).Delete(&UserPreferences{}).Error
	}

	var preferences UserPreferences
	if err := tx.Where("user_id = ?", userID).First(&preferences).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			preferences = UserPreferences{UserID: userID}
		} else {
			return err
		}
	}

	preferences.UseIdle = input.UseIdle
	preferences.IdleTimeout = input.IdleTimeout
	preferences.UseIdlePassword = input.UseIdlePassword
	preferences.IdlePin = input.IdlePin
	preferences.UseDirectLogin = input.UseDirectLogin
	preferences.UseQuadcodeLogin = input.UseQuadcodeLogin
	preferences.SendNoticesMail = input.SendNoticesMail
	preferences.Language = input.Language

	if preferences.ID == 0 {
		return tx.Create(&preferences).Error
	}
	return tx.Save(&preferences).Error
}

// Me returns the authenticated user's profile (short format).
func (uc *UserController) Me(c fiber.Ctx) error {
	claims, ok := systemUtils.ClaimsFromCtx(c)
	if !ok {
		return fiber.NewError(fiber.StatusUnauthorized, "missing claims")
	}

	db, err := db.DBFromCtx(c)
	if err != nil {
		return err
	}

	var user User
	if err := db.Preload("Details").Preload("Preferences").Where("email = ?", claims.Username).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "user not found")
		}
		return fiber.NewError(fiber.StatusInternalServerError, "failed to load user")
	}

	return c.JSON(responses.Success(&user))
}

func (us *UserController) Refresh(c fiber.Ctx) error {
	var req RefreshRequest
	if err := c.Bind().Body(&req); err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "invalid payload")
	}
	if req.RefreshToken == "" {
		return fiber.NewError(fiber.StatusBadRequest, "refresh_token is required")
	}

	claims, err := us.TockenService.ParseToken(req.RefreshToken)
	if err != nil {
		return fiber.NewError(fiber.StatusUnauthorized, err.Error())
	}
	if claims.TokenType != tokens.TokenTypeRefresh {
		return fiber.NewError(fiber.StatusUnauthorized, "refresh token required")
	}
	tokens, err := us.TockenService.GenerateTokenPair(claims.Username)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, err.Error())
	}
	return c.JSON(responses.Success(tokens))
}
