package service

import (
	"errors"
	"time"

	"golang.org/x/crypto/bcrypt"

	"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/api/models"
	"devone.aplikasi.web.id/gitea/mario/go-ohif-proxy/internal/auth"
)

var (
	ErrInvalidCredentials = errors.New("invalid credentials")
	ErrUserNotFound       = errors.New("user not found")
)

// AuthService handles authentication operations
type AuthService struct {
	jwtManager *auth.JWTManager
	// When you implement database connection, add a db client here
	// db *sqlx.DB
}

// NewAuthService creates a new authentication service
func NewAuthService(jwtManager *auth.JWTManager) *AuthService {
	return &AuthService{
		jwtManager: jwtManager,
	}
}

// Login authenticates a user and generates tokens
func (s *AuthService) Login(email, password string) (*models.LoginResponse, error) {
	// For now, use hardcoded credentials
	// TODO: In a real implementation, you would query the database
	if email == "admin" && password == "admin" {
		// Create a dummy user
		user := &models.User{
			ID:        "1",
			Email:     "admin",
			Role:      "expertise_doctor",
			Name:      "Admin User",
			CreatedAt: time.Now().Format(time.RFC3339),
			UpdatedAt: time.Now().Format(time.RFC3339),
		}

		// Generate tokens
		accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
		if err != nil {
			return nil, err
		}

		refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
		if err != nil {
			return nil, err
		}

		// TODO: In a real implementation, you would store the refresh token in the database
		// For example:
		// s.storeRefreshToken(user.ID, refreshToken)

		// Determine redirect URL based on role
		redirectURL := "/viewer"
		if user.Role == "ref_doctor" || user.Role == "expertise_doctor" {
			redirectURL = "/studylist"
		}

		return &models.LoginResponse{
			AccessToken:  accessToken,
			RefreshToken: refreshToken,
			User:         user,
			RedirectURL:  redirectURL,
		}, nil
	} else if email == "patient" && password == "patient" {
		// Create a patient user
		user := &models.User{
			ID:        "2",
			Email:     "patient",
			Role:      "patient",
			Name:      "Patient User",
			CreatedAt: time.Now().Format(time.RFC3339),
			UpdatedAt: time.Now().Format(time.RFC3339),
		}

		// Generate tokens with patient-specific claims
		accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
		if err != nil {
			return nil, err
		}

		refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
		if err != nil {
			return nil, err
		}

		return &models.LoginResponse{
			AccessToken:  accessToken,
			RefreshToken: refreshToken,
			User:         user,
			RedirectURL:  "/viewer",
		}, nil
	} else if email == "doctor" && password == "doctor" {
		// Create a referring doctor user
		user := &models.User{
			ID:        "3",
			Email:     "doctor",
			Role:      "ref_doctor",
			Name:      "Doctor User",
			CreatedAt: time.Now().Format(time.RFC3339),
			UpdatedAt: time.Now().Format(time.RFC3339),
		}

		// Generate tokens
		accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Email, user.Role)
		if err != nil {
			return nil, err
		}

		refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID, user.Email, user.Role)
		if err != nil {
			return nil, err
		}

		return &models.LoginResponse{
			AccessToken:  accessToken,
			RefreshToken: refreshToken,
			User:         user,
			RedirectURL:  "/studylist",
		}, nil
	}

	return nil, ErrInvalidCredentials
}

// RefreshToken generates a new access token using a refresh token
func (s *AuthService) RefreshToken(refreshToken string) (string, error) {
	// Validate the refresh token
	claims, err := s.jwtManager.ValidateToken(refreshToken)
	if err != nil {
		return "", err
	}

	// Check if token is a refresh token
	if claims.TokenType != "refresh" {
		return "", errors.New("invalid token type")
	}

	// TODO: In a real implementation, you would check if the token is in the database and not revoked
	// Here we just generate a new access token
	accessToken, err := s.jwtManager.GenerateAccessToken(claims.UserID, claims.Email, claims.Role)
	if err != nil {
		return "", err
	}

	return accessToken, nil
}

// ValidateToken validates a token and returns the claims
func (s *AuthService) ValidateToken(token string) (*auth.CustomClaims, error) {
	return s.jwtManager.ValidateToken(token)
}

// HashPassword hashes a password using bcrypt
func HashPassword(password string) (string, error) {
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return "", err
	}
	return string(hashedPassword), nil
}

// CheckPassword compares a password with a hash
func CheckPassword(password, hash string) error {
	return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}

// Below functions would be implemented when connecting to a real database

// storeRefreshToken stores a refresh token in the database
func (s *AuthService) storeRefreshToken(userID, token string) error {
	// TODO: In a real implementation, this would insert a record in the database
	// For example:
	/*
		refreshToken := &models.RefreshToken{
			ID:        uuid.New().String(),
			UserID:    userID,
			Token:     token,
			ExpiresAt: time.Now().Add(7 * 24 * time.Hour).Format(time.RFC3339),
			IsRevoked: false,
			CreatedAt: time.Now().Format(time.RFC3339),
		}

		_, err := s.db.NamedExec(
			`INSERT INTO refresh_tokens (id, user_id, token, expires_at, is_revoked, created_at)
			 VALUES (:id, :user_id, :token, :expires_at, :is_revoked, :created_at)`,
			refreshToken,
		)
		return err
	*/

	return nil
}

// revokeRefreshToken marks a refresh token as revoked
func (s *AuthService) revokeRefreshToken(token string) error {
	// TODO: In a real implementation, this would update a record in the database
	// For example:
	/*
		_, err := s.db.Exec(
			"UPDATE refresh_tokens SET is_revoked = true WHERE token = ?",
			token,
		)
		return err
	*/

	return nil
}
