package services

import (
	"errors"
	"lost-and-found/internal/config"
	"lost-and-found/internal/models"
	"lost-and-found/internal/repositories"
	"lost-and-found/internal/utils"

	"go.uber.org/zap"
	"gorm.io/gorm"
)

// --- 1. Definisi Interface (Contract) ---
// Interface ini mendefinisikan method apa saja yang dibutuhkan oleh Service dari Repository.
// Dengan ini, kita bisa menukar Repository asli dengan Mock Repository saat testing.

type IUserRepository interface {
	FindByEmail(email string) (*models.User, error)
	FindByNRP(nrp string) (*models.User, error)
	FindByID(id uint) (*models.User, error)
	Create(user *models.User) error
}

type IRoleRepository interface {
	FindByName(name string) (*models.Role, error)
}

type IAuditLogRepository interface {
	Log(userID *uint, action, entityType string, entityID *uint, details, ipAddress, userAgent string) error
}

// --- 2. Struct AuthService dengan Dependency Injection ---

type AuthService struct {
	userRepo     IUserRepository     // Menggunakan Interface, bukan struct konkret *repositories.UserRepository
	roleRepo     IRoleRepository     // Menggunakan Interface
	auditLogRepo IAuditLogRepository // Menggunakan Interface
	logger       *zap.Logger
}

// NewAuthService menginisialisasi service dengan repository asli (untuk Production)
func NewAuthService(db *gorm.DB, logger *zap.Logger) *AuthService {
	// ✅ Inisialisasi Enkripsi (Bonus Keamanan)
	if err := utils.InitEncryption(); err != nil {
		logger.Fatal("Failed to initialize encryption", zap.Error(err))
	}

	return &AuthService{
		// Struct repository asli secara otomatis memenuhi interface (duck typing)
		userRepo:     repositories.NewUserRepository(db),
		roleRepo:     repositories.NewRoleRepository(db),
		auditLogRepo: repositories.NewAuditLogRepository(db),
		logger:       logger,
	}
}

// --- 3. Struct Request & Response ---

type RegisterRequest struct {
	Name     string `json:"name" binding:"required"`
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required,min=6"`
	NRP      string `json:"nrp"`
	Phone    string `json:"phone"`
}

type LoginRequest struct {
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required"`
}

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

// --- 4. Implementasi Method Service ---

// Register menangani pendaftaran user baru
func (s *AuthService) Register(req RegisterRequest, ipAddress, userAgent string) (*AuthResponse, error) {
	s.logger.Info("Registration attempt",
		zap.String("email", req.Email),
		zap.String("name", req.Name),
		zap.String("ip_address", ipAddress),
	)

	// Cek apakah email sudah terdaftar
	existingUser, _ := s.userRepo.FindByEmail(req.Email)
	if existingUser != nil {
		s.logger.Warn("Registration failed: email already exists",
			zap.String("email", req.Email),
			zap.String("ip_address", ipAddress),
		)
		return nil, errors.New("email already registered")
	}

	// Cek apakah NRP sudah terdaftar (jika ada)
	if req.NRP != "" {
		existingNRP, _ := s.userRepo.FindByNRP(req.NRP)
		if existingNRP != nil {
			s.logger.Warn("Registration failed: NRP already exists",
				zap.String("email", req.Email),
			)
			return nil, errors.New("NRP already registered")
		}
	}

	// Hash password
	hashedPassword, err := utils.HashPassword(req.Password)
	if err != nil {
		s.logger.Error("Failed to hash password",
			zap.String("email", req.Email),
			zap.Error(err),
		)
		return nil, errors.New("failed to hash password")
	}

	// Ambil role default "user"
	userRole, err := s.roleRepo.FindByName(models.RoleUser)
	if err != nil {
		s.logger.Error("Failed to get user role", zap.Error(err))
		return nil, errors.New("failed to get user role")
	}

	// Buat objek User
	user := &models.User{
		Name:     req.Name,
		Email:    req.Email,
		Password: hashedPassword,
		NRP:      req.NRP,   // Disimpan plain text (atau terenkripsi jika model mendukung hook)
		Phone:    req.Phone, // Disimpan plain text
		RoleID:   userRole.ID,
		Status:   "active",
	}

	// Simpan ke database
	if err := s.userRepo.Create(user); err != nil {
		s.logger.Error("Failed to create user",
			zap.String("email", req.Email),
			zap.Error(err),
		)
		return nil, errors.New("failed to create user")
	}

	// Muat ulang user untuk mendapatkan relasi Role yang lengkap
	user, err = s.userRepo.FindByID(user.ID)
	if err != nil {
		s.logger.Error("Failed to load user",
			zap.Uint("user_id", user.ID),
			zap.Error(err),
		)
		return nil, err
	}

	// Generate JWT Token
	token, err := config.GenerateToken(user.ID, user.Email, user.Role.Name)
	if err != nil {
		s.logger.Error("Failed to generate token",
			zap.Uint("user_id", user.ID),
			zap.Error(err),
		)
		return nil, errors.New("failed to generate token")
	}

	// Log Audit
	s.auditLogRepo.Log(&user.ID, models.ActionCreate, models.EntityUser, &user.ID,
		"User registered", ipAddress, userAgent)

	s.logger.Info("Registration successful",
		zap.Uint("user_id", user.ID),
		zap.String("email", user.Email),
		zap.String("ip_address", ipAddress),
	)

	return &AuthResponse{
		Token: token,
		User:  user.ToResponse(),
	}, nil
}

// Login menangani autentikasi user
func (s *AuthService) Login(req LoginRequest, ipAddress, userAgent string) (*AuthResponse, error) {
	s.logger.Info("Login attempt",
		zap.String("email", req.Email),
		zap.String("ip_address", ipAddress),
	)

	// Cari user berdasarkan email
	user, err := s.userRepo.FindByEmail(req.Email)
	if err != nil {
		s.logger.Warn("Login failed: user not found",
			zap.String("email", req.Email),
			zap.String("ip_address", ipAddress),
		)
		return nil, errors.New("invalid email or password")
	}

	// Cek apakah akun diblokir
	if user.IsBlocked() {
		s.logger.Warn("Login failed: account blocked",
			zap.String("email", user.Email),
			zap.Uint("user_id", user.ID),
			zap.String("ip_address", ipAddress),
		)
		return nil, errors.New("account is blocked")
	}

	// Verifikasi password
	passwordMatch := utils.CheckPasswordHash(req.Password, user.Password)
	if !passwordMatch {
		s.logger.Warn("Login failed: incorrect password",
			zap.String("email", user.Email),
			zap.Uint("user_id", user.ID),
			zap.String("ip_address", ipAddress),
		)
		return nil, errors.New("invalid email or password")
	}

	// Pastikan Role ter-load (Reload jika perlu)
	if user.Role.ID == 0 {
		user, err = s.userRepo.FindByID(user.ID)
		if err != nil {
			return nil, errors.New("failed to load user data")
		}
	}

	// Generate JWT Token
	token, err := config.GenerateToken(user.ID, user.Email, user.Role.Name)
	if err != nil {
		s.logger.Error("Failed to generate token",
			zap.Uint("user_id", user.ID),
			zap.Error(err),
		)
		return nil, errors.New("failed to generate token")
	}

	// Log Audit
	s.auditLogRepo.Log(&user.ID, models.ActionLogin, models.EntityUser, &user.ID,
		"User logged in", ipAddress, userAgent)

	s.logger.Info("Login successful",
		zap.Uint("user_id", user.ID),
		zap.String("email", user.Email),
		zap.String("ip_address", ipAddress),
	)

	return &AuthResponse{
		Token: token,
		User:  user.ToResponse(),
	}, nil
}

// ValidateToken memvalidasi token JWT dan mengembalikan user terkait
func (s *AuthService) ValidateToken(tokenString string) (*models.User, error) {
	claims, err := config.ValidateToken(tokenString)
	if err != nil {
		s.logger.Warn("Token validation failed", zap.Error(err))
		return nil, errors.New("invalid token")
	}

	user, err := s.userRepo.FindByID(claims.UserID)
	if err != nil {
		return nil, errors.New("user not found")
	}

	if user.IsBlocked() {
		return nil, errors.New("account is blocked")
	}

	return user, nil
}

// RefreshToken memperbarui token JWT yang sudah ada
func (s *AuthService) RefreshToken(oldToken string) (string, error) {
	s.logger.Info("Token refresh attempt")
	
	newToken, err := config.RefreshToken(oldToken)
	if err != nil {
		s.logger.Error("Token refresh failed", zap.Error(err))
		return "", err
	}
	
	s.logger.Info("Token refreshed successfully")
	return newToken, nil
}