package handlers

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"backend/internal/apperrors"
	"backend/internal/models"
	"backend/internal/repository"
	"backend/internal/services"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgxpool"
)

type Handler struct {
	authService          *services.AuthService
	clientUserRepo       *repository.ClientUserRepository
	passwordResetService *services.PasswordResetService
	packageService       *services.InternetPackageService
	transactionService   *services.TransactionService
}

func New(db *pgxpool.Pool, jwtSecret string, jwtExpiry int, emailQueue services.EmailQueue) *Handler {
	clientUserRepo := repository.NewClientUserRepository(db)
	authService := services.NewAuthService(clientUserRepo, jwtSecret, jwtExpiry)

	// Initialize password reset services
	passwordResetRepo := repository.NewPasswordResetRepository(db)
	otpService := services.NewOTPService()
	passwordResetService := services.NewPasswordResetService(clientUserRepo, passwordResetRepo, otpService, emailQueue)

	// Initialize package service
	packageRepo := repository.NewInternetPackageRepository(db)
	packageService := services.NewInternetPackageService(packageRepo)

	// Initialize transaction service
	transactionRepo := repository.NewTransactionRepository(db)
	transactionService := services.NewTransactionService(transactionRepo)

	return &Handler{
		authService:          authService,
		clientUserRepo:       clientUserRepo,
		passwordResetService: passwordResetService,
		packageService:       packageService,
		transactionService:   transactionService,
	}
}

func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
	var req models.LoginRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request format.")
		return
	}

	// Validate input
	req.Username = strings.TrimSpace(req.Username)
	req.Password = strings.TrimSpace(req.Password)

	if req.Username == "" {
		respondWithError(w, http.StatusBadRequest, "Username or email is required.")
		return
	}

	if req.Password == "" {
		respondWithError(w, http.StatusBadRequest, "Password is required.")
		return
	}

	// Get client IP address for login tracking
	ipAddress := r.RemoteAddr
	if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
		ipAddress = forwarded
	} else if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
		ipAddress = realIP
	}

	// Add IP to context
	ctx := r.Context()
	ctx = context.WithValue(ctx, "ip_address", ipAddress)

	// Authenticate
	response, err := h.authService.Login(ctx, req.Username, req.Password)
	if err != nil {
		// Handle specific authentication errors
		switch err {
		case apperrors.ErrAccountNotFound:
			respondWithAuthError(w, http.StatusNotFound, "Account not found", "Account with this email does not exist.", "ACCOUNT_NOT_FOUND")
			return
		case apperrors.ErrIncorrectPassword:
			respondWithAuthError(w, http.StatusUnauthorized, "Incorrect password", "Incorrect password.", "INCORRECT_PASSWORD")
			return
		case apperrors.ErrAccountInactive:
			respondWithAuthError(w, http.StatusForbidden, "Account inactive", "Your account has been deactivated. Please contact support.", "ACCOUNT_INACTIVE")
			return
		case apperrors.ErrAccountSuspended:
			respondWithAuthError(w, http.StatusForbidden, "Account suspended", "Your account has been suspended. Please contact support.", "ACCOUNT_SUSPENDED")
			return
		case apperrors.ErrAccountLocked:
			respondWithAuthError(w, http.StatusForbidden, "Account locked", "Your account is temporarily locked. Please try again later.", "ACCOUNT_LOCKED")
			return
		case apperrors.ErrTokenGenerationFailed:
			respondWithError(w, http.StatusInternalServerError, "Authentication failed. Please try again.")
			return
		default:
			// Check if it's a custom AuthError
			if authErr, ok := err.(*apperrors.AuthError); ok {
				respondWithAuthError(w, http.StatusUnauthorized, authErr.Type, authErr.Message, authErr.Code)
				return
			}
			// Generic error for unexpected cases
			respondWithError(w, http.StatusInternalServerError, "An error occurred. Please try again.")
			return
		}
	}

	respondWithJSON(w, http.StatusOK, response)
}

func (h *Handler) Logout(w http.ResponseWriter, r *http.Request) {
	// In JWT-based auth, logout is handled client-side by removing the token
	// But we can add token blacklisting here if needed
	respondWithJSON(w, http.StatusOK, map[string]string{"message": "Logged out successfully"})
}

func (h *Handler) GetCurrentUser(w http.ResponseWriter, r *http.Request) {
	// Get client_user_id from context (set by auth middleware)
	clientUserIDStr, ok := r.Context().Value("client_user_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	clientUserID, err := uuid.Parse(clientUserIDStr)
	if err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid user ID")
		return
	}

	// Get client user from database
	user, err := h.clientUserRepo.FindByID(r.Context(), clientUserID)
	if err != nil {
		respondWithError(w, http.StatusNotFound, "User not found")
		return
	}

	// Return user info (without password)
	userInfo := models.ClientUserInfo{
		ID:        user.ID,
		ClientID:  user.ClientID,
		FirstName: user.FirstName,
		LastName:  user.LastName,
		Email:     user.Email,
		Phone:     user.Phone,
		Username:  user.Username,
		Role:      user.Role,
		Status:    user.Status,
		IsActive:  user.IsActive,
		AvatarURL: user.AvatarURL,
		LastLogin: user.LastLogin,
		CreatedAt: user.CreatedAt,
	}

	respondWithJSON(w, http.StatusOK, userInfo)
}

func respondWithJSON(w http.ResponseWriter, status int, payload interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(payload)
}

func respondWithError(w http.ResponseWriter, status int, message string) {
	respondWithJSON(w, status, map[string]string{
		"error":   message,
		"success": "false",
	})
}

func respondWithAuthError(w http.ResponseWriter, status int, title, message, code string) {
	respondWithJSON(w, status, map[string]interface{}{
		"error":   title,
		"message": message,
		"code":    code,
		"success": false,
	})
}

func (h *Handler) GetAuthService() *services.AuthService {
	return h.authService
}

func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
	var req models.ForgotPasswordRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request format.")
		return
	}

	// Validate input
	req.Email = strings.TrimSpace(req.Email)
	if req.Email == "" {
		respondWithError(w, http.StatusBadRequest, "Email is required.")
		return
	}

	// Request password reset
	err := h.passwordResetService.RequestPasswordReset(r.Context(), req.Email)
	if err != nil {
		// Log the error for debugging
		log.Printf("ForgotPassword error for email %s: %v", req.Email, err)

		// Handle specific errors using errors.Is for wrapped errors
		if errors.Is(err, apperrors.ErrAccountNotFound) {
			log.Printf("Account not found for email: %s", req.Email)
			respondWithAuthError(w, http.StatusNotFound, "Account not found", "Account with this email does not exist.", "ACCOUNT_NOT_FOUND")
			return
		}

		if errors.Is(err, apperrors.ErrAccountInactive) {
			log.Printf("Account inactive for email: %s", req.Email)
			respondWithAuthError(w, http.StatusForbidden, "Account inactive", "Your account has been deactivated. Please contact support.", "ACCOUNT_INACTIVE")
			return
		}

		// Check if it's a custom AuthError
		var authErr *apperrors.AuthError
		if errors.As(err, &authErr) {
			log.Printf("Auth error for email %s: %s - %s", req.Email, authErr.Code, authErr.Message)
			respondWithAuthError(w, http.StatusBadRequest, authErr.Type, authErr.Message, authErr.Code)
			return
		}

		// For other errors (like email sending failures, database errors, etc.)
		log.Printf("Unexpected error in ForgotPassword for email %s: %v", req.Email, err)
		respondWithError(w, http.StatusInternalServerError, "An error occurred while processing your request. Please try again.")
		return
	}

	// Success - email sent
	respondWithJSON(w, http.StatusOK, models.ForgotPasswordResponse{
		Message: "Password reset OTP has been sent to your email.",
		Success: true,
	})
}

func (h *Handler) VerifyOTP(w http.ResponseWriter, r *http.Request) {
	var req models.VerifyOTPRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request format.")
		return
	}

	// Validate input
	req.Email = strings.TrimSpace(req.Email)
	req.OTP = strings.TrimSpace(req.OTP)

	if req.Email == "" {
		respondWithError(w, http.StatusBadRequest, "Email is required.")
		return
	}

	if req.OTP == "" || len(req.OTP) != 6 {
		respondWithError(w, http.StatusBadRequest, "OTP must be 6 digits.")
		return
	}

	// Verify OTP
	resetToken, err := h.passwordResetService.VerifyOTP(r.Context(), req.Email, req.OTP)
	if err != nil {
		if authErr, ok := err.(*apperrors.AuthError); ok {
			respondWithAuthError(w, http.StatusUnauthorized, authErr.Type, authErr.Message, authErr.Code)
			return
		}
		respondWithError(w, http.StatusInternalServerError, "An error occurred. Please try again.")
		return
	}

	respondWithJSON(w, http.StatusOK, models.VerifyOTPResponse{
		Message:    "OTP verified successfully. You can now reset your password.",
		ResetToken: resetToken,
		Success:    true,
	})
}

func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
	var req models.ResetPasswordRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request format.")
		return
	}

	// Validate input
	req.ResetToken = strings.TrimSpace(req.ResetToken)
	req.Password = strings.TrimSpace(req.Password)

	if req.ResetToken == "" {
		respondWithError(w, http.StatusBadRequest, "Reset token is required.")
		return
	}

	if req.Password == "" || len(req.Password) < 6 {
		respondWithError(w, http.StatusBadRequest, "Password must be at least 6 characters.")
		return
	}

	// Reset password
	err := h.passwordResetService.ResetPassword(r.Context(), req.ResetToken, req.Password)
	if err != nil {
		if authErr, ok := err.(*apperrors.AuthError); ok {
			respondWithAuthError(w, http.StatusUnauthorized, authErr.Type, authErr.Message, authErr.Code)
			return
		}
		respondWithError(w, http.StatusInternalServerError, "An error occurred. Please try again.")
		return
	}

	respondWithJSON(w, http.StatusOK, models.ResetPasswordResponse{
		Message: "Password reset successfully. You can now login with your new password.",
		Success: true,
	})
}

// Package handlers

// CreatePackage creates a new internet package
func (h *Handler) CreatePackage(w http.ResponseWriter, r *http.Request) {
	// Get client_id from context (set by auth middleware)
	clientID, ok := r.Context().Value("client_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	var req models.CreateInternetPackageRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request format.")
		return
	}

	// Validate required fields
	if req.PackageType == "" {
		respondWithError(w, http.StatusBadRequest, "Package type is required.")
		return
	}
	if req.Name == "" {
		respondWithError(w, http.StatusBadRequest, "Package name is required.")
		return
	}
	if req.Amount < 0 {
		respondWithError(w, http.StatusBadRequest, "Amount must be greater than or equal to 0.")
		return
	}

	pkg, err := h.packageService.CreatePackage(r.Context(), clientID, &req)
	if err != nil {
		respondWithError(w, http.StatusInternalServerError, "Failed to create package.")
		return
	}

	respondWithJSON(w, http.StatusCreated, map[string]interface{}{
		"message": "Package created successfully",
		"data":    pkg,
	})
}

// GetPackage retrieves a package by ID
func (h *Handler) GetPackage(w http.ResponseWriter, r *http.Request) {
	// Get client_id from context
	clientID, ok := r.Context().Value("client_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Get package ID from URL parameter (using chi router)
	packageIDStr := chi.URLParam(r, "id")
	packageID, err := uuid.Parse(packageIDStr)
	if err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid package ID.")
		return
	}

	pkg, err := h.packageService.GetPackage(r.Context(), packageID, clientID)
	if err != nil {
		if err.Error() == "internet package not found" {
			respondWithError(w, http.StatusNotFound, "Package not found.")
			return
		}
		respondWithError(w, http.StatusInternalServerError, "Failed to retrieve package.")
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Package retrieved successfully",
		"data":    pkg,
	})
}

// GetAllPackages retrieves all packages for a client
func (h *Handler) GetAllPackages(w http.ResponseWriter, r *http.Request) {
	// Get client_id from context
	clientID, ok := r.Context().Value("client_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Parse query parameters
	activeOnly := r.URL.Query().Get("active_only") == "true"
	limit := 10 // default to 10 per page
	offset := 0

	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
			limit = parsedLimit
		}
	}
	if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
		if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
			offset = parsedOffset
		}
	}

	// Parse filter parameters
	var packageType *string
	if pt := r.URL.Query().Get("package_type"); pt != "" && pt != "all" {
		packageType = &pt
	}

	var searchQuery *string
	if sq := r.URL.Query().Get("search"); sq != "" {
		searchQuery = &sq
	}

	packages, totalCount, err := h.packageService.GetAllPackages(r.Context(), clientID, activeOnly, packageType, searchQuery, limit, offset)
	if err != nil {
		respondWithError(w, http.StatusInternalServerError, "Failed to retrieve packages.")
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Packages retrieved successfully",
		"data":    packages,
		"count":   len(packages),
		"total":   totalCount,
	})
}

// UpdatePackage updates an existing package
func (h *Handler) UpdatePackage(w http.ResponseWriter, r *http.Request) {
	// Get client_id from context
	clientID, ok := r.Context().Value("client_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Get package ID from URL parameter
	packageIDStr := chi.URLParam(r, "id")
	packageID, err := uuid.Parse(packageIDStr)
	if err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid package ID.")
		return
	}

	var req models.UpdateInternetPackageRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request format.")
		return
	}

	// Validate amount if provided
	if req.Amount != nil && *req.Amount < 0 {
		respondWithError(w, http.StatusBadRequest, "Amount must be greater than or equal to 0.")
		return
	}

	pkg, err := h.packageService.UpdatePackage(r.Context(), packageID, clientID, &req)
	if err != nil {
		if err.Error() == "internet package not found" {
			respondWithError(w, http.StatusNotFound, "Package not found.")
			return
		}
		respondWithError(w, http.StatusInternalServerError, "Failed to update package.")
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Package updated successfully",
		"data":    pkg,
	})
}

// DeletePackage deletes a package
func (h *Handler) DeletePackage(w http.ResponseWriter, r *http.Request) {
	// Get client_id from context
	clientID, ok := r.Context().Value("client_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Get package ID from URL parameter
	packageIDStr := chi.URLParam(r, "id")
	packageID, err := uuid.Parse(packageIDStr)
	if err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid package ID.")
		return
	}

	err = h.packageService.DeletePackage(r.Context(), packageID, clientID)
	if err != nil {
		if err.Error() == "internet package not found" {
			respondWithError(w, http.StatusNotFound, "Package not found.")
			return
		}
		respondWithError(w, http.StatusInternalServerError, "Failed to delete package.")
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Package deleted successfully",
	})
}

// GetTransaction retrieves a single transaction by ID
func (h *Handler) GetTransaction(w http.ResponseWriter, r *http.Request) {
	// Get client_id from context
	clientID, ok := r.Context().Value("client_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Get transaction ID from URL
	idStr := chi.URLParam(r, "id")
	id, err := uuid.Parse(idStr)
	if err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid transaction ID.")
		return
	}

	transaction, err := h.transactionService.GetTransaction(r.Context(), id, clientID)
	if err != nil {
		if err.Error() == "transaction not found" {
			respondWithError(w, http.StatusNotFound, "Transaction not found.")
			return
		}
		log.Printf("Error retrieving transaction %s for client %s: %v", id.String(), clientID, err)
		respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to retrieve transaction: %v", err))
		return
	}

	respondWithJSON(w, http.StatusOK, map[string]interface{}{
		"message":     "Transaction retrieved successfully",
		"transaction": transaction,
	})
}

// GetAllTransactions retrieves all transactions for a client
func (h *Handler) GetAllTransactions(w http.ResponseWriter, r *http.Request) {
	// Get client_id from context
	clientID, ok := r.Context().Value("client_id").(string)
	if !ok {
		respondWithError(w, http.StatusUnauthorized, "Unauthorized")
		return
	}

	// Parse query parameters
	limit := 10 // default to 10 per page
	offset := 0

	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
			limit = parsedLimit
		}
	}
	if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
		if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
			offset = parsedOffset
		}
	}

	// Parse filter parameters
	var paymentStatus *string
	if ps := r.URL.Query().Get("payment_status"); ps != "" && ps != "all" {
		paymentStatus = &ps
	}

	var paymentMethod *string
	if pm := r.URL.Query().Get("payment_method"); pm != "" && pm != "all" {
		paymentMethod = &pm
	}

	var searchQuery *string
	if sq := r.URL.Query().Get("search"); sq != "" {
		searchQuery = &sq
	}

	response, err := h.transactionService.GetAllTransactions(r.Context(), clientID, limit, offset, paymentStatus, paymentMethod, searchQuery)
	if err != nil {
		log.Printf("Error retrieving transactions for client %s: %v", clientID, err)
		respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to retrieve transactions: %v", err))
		return
	}

	respondWithJSON(w, http.StatusOK, response)
}
