package auth

import (
	"net/http"
	"strings"
	"time"

	"github.com/atlasos/calypso/internal/common/config"
	"github.com/atlasos/calypso/internal/common/database"
	"github.com/atlasos/calypso/internal/common/logger"
	"github.com/atlasos/calypso/internal/common/password"
	"github.com/atlasos/calypso/internal/iam"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
)

// Handler handles authentication requests
type Handler struct {
	db     *database.DB
	config *config.Config
	logger *logger.Logger
}

// NewHandler creates a new auth handler
func NewHandler(db *database.DB, cfg *config.Config, log *logger.Logger) *Handler {
	return &Handler{
		db:     db,
		config: cfg,
		logger: log,
	}
}

// LoginRequest represents a login request
type LoginRequest struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

// LoginResponse represents a login response
type LoginResponse struct {
	Token     string    `json:"token"`
	ExpiresAt time.Time `json:"expires_at"`
	User      UserInfo  `json:"user"`
}

// UserInfo represents user information in auth responses
type UserInfo struct {
	ID       string   `json:"id"`
	Username string   `json:"username"`
	Email    string   `json:"email"`
	FullName string   `json:"full_name"`
	Roles    []string `json:"roles"`
}

// Login handles user login
func (h *Handler) Login(c *gin.Context) {
	var req LoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
		return
	}

	// Get user from database
	user, err := iam.GetUserByUsername(h.db, req.Username)
	if err != nil {
		h.logger.Warn("Login attempt failed", "username", req.Username, "error", "user not found")
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
		return
	}

	// Check if user is active
	if !user.IsActive {
		c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled"})
		return
	}

	// Verify password
	if !h.verifyPassword(req.Password, user.PasswordHash) {
		h.logger.Warn("Login attempt failed", "username", req.Username, "error", "invalid password")
		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
		return
	}

	// Generate JWT token
	token, expiresAt, err := h.generateToken(user)
	if err != nil {
		h.logger.Error("Failed to generate token", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
		return
	}

	// Create session
	if err := h.createSession(user.ID, token, c.ClientIP(), c.GetHeader("User-Agent"), expiresAt); err != nil {
		h.logger.Error("Failed to create session", "error", err)
		// Continue anyway, token is still valid
	}

	// Update last login
	if err := h.updateLastLogin(user.ID); err != nil {
		h.logger.Warn("Failed to update last login", "error", err)
	}

	// Get user roles
	roles, err := iam.GetUserRoles(h.db, user.ID)
	if err != nil {
		h.logger.Warn("Failed to get user roles", "error", err)
		roles = []string{}
	}

	h.logger.Info("User logged in successfully", "username", req.Username, "user_id", user.ID)

	c.JSON(http.StatusOK, LoginResponse{
		Token:     token,
		ExpiresAt: expiresAt,
		User: UserInfo{
			ID:       user.ID,
			Username: user.Username,
			Email:    user.Email,
			FullName: user.FullName,
			Roles:    roles,
		},
	})
}

// Logout handles user logout
func (h *Handler) Logout(c *gin.Context) {
	// Extract token
	authHeader := c.GetHeader("Authorization")
	if authHeader != "" {
		parts := strings.SplitN(authHeader, " ", 2)
		if len(parts) == 2 && parts[0] == "Bearer" {
			// Invalidate session (token hash would be stored)
			// For now, just return success
		}
	}

	c.JSON(http.StatusOK, gin.H{"message": "logged out successfully"})
}

// Me returns current user information
func (h *Handler) Me(c *gin.Context) {
	user, exists := c.Get("user")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "authentication required"})
		return
	}

	authUser, ok := user.(*iam.User)
	if !ok {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user context"})
		return
	}

	roles, err := iam.GetUserRoles(h.db, authUser.ID)
	if err != nil {
		h.logger.Warn("Failed to get user roles", "error", err)
		roles = []string{}
	}

	c.JSON(http.StatusOK, UserInfo{
		ID:       authUser.ID,
		Username: authUser.Username,
		Email:    authUser.Email,
		FullName: authUser.FullName,
		Roles:    roles,
	})
}

// ValidateToken validates a JWT token and returns the user
func (h *Handler) ValidateToken(tokenString string) (*iam.User, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, jwt.ErrSignatureInvalid
		}
		return []byte(h.config.Auth.JWTSecret), nil
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, jwt.ErrSignatureInvalid
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, jwt.ErrInvalidKey
	}

	userID, ok := claims["user_id"].(string)
	if !ok {
		return nil, jwt.ErrInvalidKey
	}

	// Get user from database
	user, err := iam.GetUserByID(h.db, userID)
	if err != nil {
		return nil, err
	}

	if !user.IsActive {
		return nil, jwt.ErrInvalidKey
	}

	return user, nil
}

// verifyPassword verifies a password against an Argon2id hash
func (h *Handler) verifyPassword(pwd, hash string) bool {
	valid, err := password.VerifyPassword(pwd, hash)
	if err != nil {
		h.logger.Warn("Password verification error", "error", err)
		return false
	}
	return valid
}

// generateToken generates a JWT token for a user
func (h *Handler) generateToken(user *iam.User) (string, time.Time, error) {
	expiresAt := time.Now().Add(h.config.Auth.TokenLifetime)

	claims := jwt.MapClaims{
		"user_id":  user.ID,
		"username": user.Username,
		"exp":      expiresAt.Unix(),
		"iat":      time.Now().Unix(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString([]byte(h.config.Auth.JWTSecret))
	if err != nil {
		return "", time.Time{}, err
	}

	return tokenString, expiresAt, nil
}

// createSession creates a session record in the database
func (h *Handler) createSession(userID, token, ipAddress, userAgent string, expiresAt time.Time) error {
	// Hash the token for storage using SHA-256
	tokenHash := HashToken(token)

	query := `
		INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at)
		VALUES ($1, $2, $3, $4, $5)
	`
	_, err := h.db.Exec(query, userID, tokenHash, ipAddress, userAgent, expiresAt)
	return err
}

// updateLastLogin updates the user's last login timestamp
func (h *Handler) updateLastLogin(userID string) error {
	query := `UPDATE users SET last_login_at = NOW() WHERE id = $1`
	_, err := h.db.Exec(query, userID)
	return err
}


