package services

import (
	"context"
	"fmt"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
	"golang.org/x/crypto/bcrypt"
	"ticketing-backend/internal/utils"
)

const (
	AccessTokenExpiry  = 8 * 60 * 60       // 8 hours
	RefreshTokenExpiry = 30 * 24 * 60 * 60 // 30 days
)

type AuthService struct {
	pool *pgxpool.Pool
}

func NewAuthService(pool *pgxpool.Pool) *AuthService {
	return &AuthService{pool: pool}
}

type AuthTokens struct {
	AccessToken  string `json:"accessToken"`
	RefreshToken string `json:"refreshToken"`
	ExpiresIn    int    `json:"expiresIn"`
}

func (s *AuthService) GenerateTokens(userID, email, role string) (*AuthTokens, error) {
	accessToken, err := utils.EncodeJWT(map[string]interface{}{
		"userId": userID,
		"email":  email,
		"role":   role,
		"type":   "access",
	}, AccessTokenExpiry)
	if err != nil {
		return nil, fmt.Errorf("failed to generate access token: %w", err)
	}

	refreshToken, err := utils.EncodeJWT(map[string]interface{}{
		"userId": userID,
		"type":   "refresh",
	}, RefreshTokenExpiry)
	if err != nil {
		return nil, fmt.Errorf("failed to generate refresh token: %w", err)
	}

	// Store refresh token in database
	_, err = s.pool.Exec(context.Background(),
		`INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES ($1, $2, $3)`,
		userID, refreshToken, time.Now().Add(30*24*time.Hour),
	)
	if err != nil {
		return nil, fmt.Errorf("failed to store refresh token: %w", err)
	}

	return &AuthTokens{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		ExpiresIn:    AccessTokenExpiry,
	}, nil
}

func (s *AuthService) VerifyToken(tokenString string) (map[string]interface{}, error) {
	claims, err := utils.DecodeJWT(tokenString)
	if err != nil {
		return nil, err
	}
	return claims, nil
}

func (s *AuthService) HashPassword(password string) (string, error) {
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
	if err != nil {
		return "", err
	}
	return string(bytes), nil
}

func (s *AuthService) VerifyPassword(password, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

func (s *AuthService) RefreshAccessToken(refreshToken string) (*AuthTokens, error) {
	claims, err := s.VerifyToken(refreshToken)
	if err != nil {
		return nil, fmt.Errorf("invalid refresh token")
	}

	if claims["type"] != "refresh" {
		return nil, fmt.Errorf("invalid token type")
	}

	userID, ok := claims["userId"].(string)
	if !ok {
		return nil, fmt.Errorf("invalid token payload")
	}

	// Verify token exists in database and not revoked
	var revokedAt *time.Time
	err = s.pool.QueryRow(context.Background(),
		`SELECT revoked_at FROM refresh_tokens WHERE token = $1 AND user_id = $2`,
		refreshToken, userID,
	).Scan(&revokedAt)

	if err != nil {
		return nil, fmt.Errorf("refresh token not found")
	}

	if revokedAt != nil {
		return nil, fmt.Errorf("refresh token has been revoked")
	}

	// Get user info
	var email, role string
	err = s.pool.QueryRow(context.Background(),
		`SELECT email, role FROM users WHERE id = $1`, userID,
	).Scan(&email, &role)

	if err != nil {
		return nil, fmt.Errorf("user not found")
	}

	return s.GenerateTokens(userID, email, role)
}

func (s *AuthService) RevokeRefreshToken(refreshToken string) error {
	_, err := s.pool.Exec(context.Background(),
		`UPDATE refresh_tokens SET revoked_at = NOW() WHERE token = $1`,
		refreshToken,
	)
	return err
}

func (s *AuthService) GetDashboardPath(role string) string {
	paths := map[string]string{
		"visitor":     "/visitor/events",
		"ticketing":   "/ticketing/validation",
		"admin_event": "/admin/events",
		"wali_kelas":  "/wali-kelas/dashboard",
		"super_admin": "/super-admin/dashboard",
	}
	if path, ok := paths[role]; ok {
		return path
	}
	return "/login"
}

func (s *AuthService) LogAuthAction(userID, action, status, ipAddress string) {
	_, err := s.pool.Exec(context.Background(),
		`INSERT INTO system_logs (log_type, user_id, action, status, ip_address) VALUES ($1, $2, $3, $4, $5)`,
		"auth", userID, action, status, ipAddress,
	)
	if err != nil {
		fmt.Printf("Failed to log auth action: %v\n", err)
	}
}
