package virtualgameservice

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"
	"time"

	"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
	jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
)

type service struct {
	repo      repository.VirtualGameRepository
	walletSvc wallet.Service
	store     *repository.Store
	// virtualGameStore repository.VirtualGameRepository
	config *config.Config
	logger *slog.Logger
}

func New(repo repository.VirtualGameRepository, walletSvc wallet.Service, store *repository.Store, cfg *config.Config, logger *slog.Logger) VirtualGameService {
	return &service{
		repo:      repo,
		walletSvc: walletSvc,
		store:     store,
		config:    cfg,
		logger:    logger}
}

func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {
	user, err := s.store.GetUserByID(ctx, userID)
	if err != nil {
		s.logger.Error("Failed to get user", "userID", userID, "error", err)
		return "", err
	}

	sessionToken := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano())
	token, err := jwtutil.CreatePopOKJwt(
		userID,
		user.PhoneNumber,
		currency,
		"en",
		mode,
		sessionToken,
		s.config.PopOK.SecretKey,
		24*time.Hour,
	)
	if err != nil {
		s.logger.Error("Failed to create PopOK JWT", "userID", userID, "error", err)
		return "", err
	}

	params := fmt.Sprintf(
		"partnerId=%s&gameId=%s&gameMode=%s&lang=en&platform=%s&externalToken=%s",
		s.config.PopOK.ClientID, gameID, mode, s.config.PopOK.Platform, token,
	)

	// params = fmt.Sprintf(
	// 	"partnerId=%s&gameId=%sgameMode=%s&lang=en&platform=%s",
	// 	"1", "1", "fun", "111",
	// )

	// signature := s.generateSignature(params)
	return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
	// return fmt.Sprintf("%s?%s", s.config.PopOK.BaseURL, params), nil
}

func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error {
	s.logger.Info("Handling PopOK callback", "transactionID", callback.TransactionID, "type", callback.Type)

	if !s.verifySignature(callback) {
		s.logger.Error("Invalid callback signature", "transactionID", callback.TransactionID)
		return errors.New("invalid signature")
	}

	existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, callback.TransactionID)
	if err != nil {
		s.logger.Error("Failed to check existing transaction", "transactionID", callback.TransactionID, "error", err)
		return err
	}
	if existingTx != nil {
		s.logger.Warn("Transaction already processed", "transactionID", callback.TransactionID)
		return nil // Idempotency
	}

	session, err := s.repo.GetVirtualGameSessionByToken(ctx, callback.SessionID)
	if err != nil || session == nil {
		s.logger.Error("Invalid or missing session", "sessionID", callback.SessionID, "error", err)
		return errors.New("invalid session")
	}

	wallets, err := s.walletSvc.GetWalletsByUser(ctx, session.UserID)
	if err != nil || len(wallets) == 0 {
		s.logger.Error("Failed to get wallets or no wallet found", "userID", session.UserID, "error", err)
		return errors.New("user has no wallet")
	}

	walletID := wallets[0].ID
	amount := int64(callback.Amount * 100) // Convert to cents
	transactionType := callback.Type

	switch transactionType {
	case "BET":
		amount = -amount // Debit for bets
	case "WIN", "JACKPOT_WIN", "REFUND":
	default:
		s.logger.Error("Unknown transaction type", "transactionID", callback.TransactionID, "type", transactionType)
		return errors.New("unknown transaction type")
	}

	err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(amount))
	if err != nil {
		s.logger.Error("Failed to update wallet", "walletID", walletID, "userID", session.UserID, "amount", amount, "error", err)
		return err
	}

	// Record transaction
	tx := &domain.VirtualGameTransaction{
		SessionID:             session.ID,
		UserID:                session.UserID,
		WalletID:              walletID,
		TransactionType:       transactionType,
		Amount:                amount,
		Currency:              callback.Currency,
		ExternalTransactionID: callback.TransactionID,
		Status:                "COMPLETED",
		CreatedAt:             time.Now(),
		UpdatedAt:             time.Now(),
	}

	if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
		s.logger.Error("Failed to create transaction", "transactionID", callback.TransactionID, "error", err)
		return err
	}

	s.logger.Info("Callback processed successfully", "transactionID", callback.TransactionID, "type", transactionType, "amount", callback.Amount)
	return nil
}

func (s *service) GetPlayerInfo(ctx context.Context, req *domain.PopOKPlayerInfoRequest) (*domain.PopOKPlayerInfoResponse, error) {
	claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
	if err != nil {
		s.logger.Error("Failed to parse JWT", "error", err)
		return nil, fmt.Errorf("invalid token")
	}

	wallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
	if err != nil || len(wallets) == 0 {
		s.logger.Error("No wallets found for user", "userID", claims.UserID)
		return nil, fmt.Errorf("no wallet found")
	}

	return &domain.PopOKPlayerInfoResponse{
		Country:  "ET",
		Currency: claims.Currency,
		Balance:  float64(wallets[0].Balance) / 100, // Convert cents to currency
		PlayerID: fmt.Sprintf("%d", claims.UserID),
	}, nil
}

func (s *service) ProcessBet(ctx context.Context, req *domain.PopOKBetRequest) (*domain.PopOKBetResponse, error) {
	// Validate token and get user ID
	claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
	if err != nil {
		return nil, fmt.Errorf("invalid token")
	}

	// Convert amount to cents (assuming wallet uses cents)
	amountCents := int64(req.Amount * 100)

	// Deduct from wallet

	userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
	if err != nil {
		return &domain.PopOKBetResponse{}, fmt.Errorf("Failed to read user wallets")
	}

	if err := s.walletSvc.DeductFromWallet(ctx, claims.UserID, domain.Currency(amountCents)); err != nil {
		return nil, fmt.Errorf("insufficient balance")
	}

	// Create transaction record
	tx := &domain.VirtualGameTransaction{
		UserID:                claims.UserID,
		TransactionType:       "BET",
		Amount:                -amountCents, // Negative for bets
		Currency:              req.Currency,
		ExternalTransactionID: req.TransactionID,
		Status:                "COMPLETED",
		CreatedAt:             time.Now(),
	}

	if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
		s.logger.Error("Failed to create bet transaction", "error", err)
		return nil, fmt.Errorf("transaction failed")
	}

	return &domain.PopOKBetResponse{
		TransactionID: req.TransactionID,
		ExternalTrxID: fmt.Sprintf("%v", tx.ID), // Your internal transaction ID
		Balance:       float64(userWallets[0].Balance) / 100,
	}, nil
}

func (s *service) ProcessWin(ctx context.Context, req *domain.PopOKWinRequest) (*domain.PopOKWinResponse, error) {
	// 1. Validate token and get user ID
	claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
	if err != nil {
		s.logger.Error("Invalid token in win request", "error", err)
		return nil, fmt.Errorf("invalid token")
	}

	// 2. Check for duplicate transaction (idempotency)
	existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
	if err != nil {
		s.logger.Error("Failed to check existing transaction", "error", err)
		return nil, fmt.Errorf("transaction check failed")
	}

	if existingTx != nil && existingTx.TransactionType == "WIN" {
		s.logger.Warn("Duplicate win transaction", "transactionID", req.TransactionID)
		wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
		balance := 0.0
		if len(wallets) > 0 {
			balance = float64(wallets[0].Balance) / 100
		}
		return &domain.PopOKWinResponse{
			TransactionID: req.TransactionID,
			ExternalTrxID: fmt.Sprintf("%d", existingTx.ID),
			Balance:       balance,
		}, nil
	}

	// 3. Convert amount to cents
	amountCents := int64(req.Amount * 100)

	// 4. Credit to wallet

	if err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(amountCents)); err != nil {
		s.logger.Error("Failed to credit wallet", "userID", claims.UserID, "error", err)
		return nil, fmt.Errorf("wallet credit failed")
	}

	userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
	if err != nil {
		return &domain.PopOKWinResponse{}, fmt.Errorf("Failed to read user wallets")
	}
	// 5. Create transaction record
	tx := &domain.VirtualGameTransaction{
		UserID:                claims.UserID,
		TransactionType:       "WIN",
		Amount:                amountCents,
		Currency:              req.Currency,
		ExternalTransactionID: req.TransactionID,
		Status:                "COMPLETED",
		CreatedAt:             time.Now(),
	}

	if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
		s.logger.Error("Failed to create win transaction", "error", err)
		return nil, fmt.Errorf("transaction recording failed")
	}

	return &domain.PopOKWinResponse{
		TransactionID: req.TransactionID,
		ExternalTrxID: fmt.Sprintf("%v", tx.ID),
		Balance:       float64(userWallets[0].Balance) / 100,
	}, nil
}

func (s *service) ProcessCancel(ctx context.Context, req *domain.PopOKCancelRequest) (*domain.PopOKCancelResponse, error) {
	// 1. Validate token and get user ID
	claims, err := jwtutil.ParsePopOKJwt(req.ExternalToken, s.config.PopOK.SecretKey)
	if err != nil {
		s.logger.Error("Invalid token in cancel request", "error", err)
		return nil, fmt.Errorf("invalid token")
	}

	// 2. Find the original bet transaction
	originalBet, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, req.TransactionID)
	if err != nil {
		s.logger.Error("Failed to find original bet", "transactionID", req.TransactionID, "error", err)
		return nil, fmt.Errorf("original bet not found")
	}

	// 3. Validate the original transaction
	if originalBet == nil || originalBet.TransactionType != "BET" {
		s.logger.Error("Invalid original transaction for cancel", "transactionID", req.TransactionID)
		return nil, fmt.Errorf("invalid original transaction")
	}

	// 4. Check if already cancelled
	if originalBet.Status == "CANCELLED" {
		s.logger.Warn("Transaction already cancelled", "transactionID", req.TransactionID)
		wallets, _ := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
		balance := 0.0
		if len(wallets) > 0 {
			balance = float64(wallets[0].Balance) / 100
		}
		return &domain.PopOKCancelResponse{
			TransactionID: req.TransactionID,
			ExternalTrxID: fmt.Sprintf("%v", originalBet.ID),
			Balance:       balance,
		}, nil
	}

	// 5. Refund the bet amount (absolute value since bet amount is negative)
	refundAmount := -originalBet.Amount

	if err := s.walletSvc.AddToWallet(ctx, claims.UserID, domain.Currency(refundAmount)); err != nil {
		s.logger.Error("Failed to refund bet", "userID", claims.UserID, "error", err)
		return nil, fmt.Errorf("refund failed")
	}

	userWallets, err := s.walletSvc.GetWalletsByUser(ctx, claims.UserID)
	if err != nil {
		return &domain.PopOKCancelResponse{}, fmt.Errorf("Failed to read user wallets")
	}

	// 6. Mark original bet as cancelled and create cancel record
	cancelTx := &domain.VirtualGameTransaction{
		UserID:                 claims.UserID,
		TransactionType:        "CANCEL",
		Amount:                 refundAmount,
		Currency:               originalBet.Currency,
		ExternalTransactionID:  req.TransactionID,
		ReferenceTransactionID: fmt.Sprintf("%v", originalBet.ID),
		Status:                 "COMPLETED",
		CreatedAt:              time.Now(),
	}

	if err := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, "CANCELLED"); err != nil {
		s.logger.Error("Failed to update transaction status", "error", err)
		return nil, fmt.Errorf("update failed")
	}

	// Create cancel transaction
	if err := s.repo.CreateVirtualGameTransaction(ctx, cancelTx); err != nil {
		s.logger.Error("Failed to create cancel transaction", "error", err)

		// Attempt to revert the status update
		if revertErr := s.repo.UpdateVirtualGameTransactionStatus(ctx, originalBet.ID, originalBet.Status); revertErr != nil {
			s.logger.Error("Failed to revert transaction status", "error", revertErr)
		}

		return nil, fmt.Errorf("create failed")
	}

	// if err != nil {
	// 	s.logger.Error("Failed to process cancel transaction", "error", err)
	// 	return nil, fmt.Errorf("transaction processing failed")
	// }

	return &domain.PopOKCancelResponse{
		TransactionID: req.TransactionID,
		ExternalTrxID: fmt.Sprintf("%v", cancelTx.ID),
		Balance:       float64(userWallets[0].Balance) / 100,
	}, nil
}

func (s *service) GenerateSignature(params string) string {
	h := hmac.New(sha256.New, []byte(s.config.PopOK.SecretKey))
	h.Write([]byte(params))
	return hex.EncodeToString(h.Sum(nil))
}

func (s *service) verifySignature(callback *domain.PopOKCallback) bool {
	data, _ := json.Marshal(struct {
		TransactionID string  `json:"transaction_id"`
		SessionID     string  `json:"session_id"`
		Type          string  `json:"type"`
		Amount        float64 `json:"amount"`
		Currency      string  `json:"currency"`
		Timestamp     int64   `json:"timestamp"`
	}{
		TransactionID: callback.TransactionID,
		SessionID:     callback.SessionID,
		Type:          callback.Type,
		Amount:        callback.Amount,
		Currency:      callback.Currency,
		Timestamp:     callback.Timestamp,
	})

	h := hmac.New(sha256.New, []byte(s.config.PopOK.SecretKey))
	h.Write(data)
	expected := hex.EncodeToString(h.Sum(nil))
	return expected == callback.Signature
}

func (s *service) GetGameCounts(ctx context.Context, filter domain.ReportFilter) (total, active, inactive int64, err error) {
	return s.repo.GetGameCounts(ctx, filter)
}
