package veli

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"log/slog"
	"net/url"
	"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"
)

type VeliPlayService struct {
	repo      repository.VirtualGameRepository
	walletSvc wallet.Service
	config    *config.VeliGamesConfig
	logger    *slog.Logger
}

func NewVeliPlayService(
	repo repository.VirtualGameRepository,
	walletSvc wallet.Service,
	cfg *config.Config,
	logger *slog.Logger,
) *VeliPlayService {
	return &VeliPlayService{
		repo:      repo,
		walletSvc: walletSvc,
		config:    &cfg.VeliGames,
		logger:    logger,
	}
}

func (s *VeliPlayService) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {
	session := &domain.VirtualGameSession{
		UserID:       userID,
		GameID:       gameID,
		SessionToken: generateSessionToken(userID),
		Currency:     currency,
		Status:       "ACTIVE",
		CreatedAt:    time.Now(),
		UpdatedAt:    time.Now(),
		ExpiresAt:    time.Now().Add(24 * time.Hour),
	}

	if err := s.repo.CreateVirtualGameSession(ctx, session); err != nil {
		return "", fmt.Errorf("failed to create game session: %w", err)
	}

	// Veli-specific parameters
	params := url.Values{
		"operator_key": []string{s.config.OperatorKey}, // Different from Alea's operator_id
		"user_id":      []string{fmt.Sprintf("%d", userID)},
		"game_id":      []string{gameID},
		"currency":     []string{currency},
		"mode":         []string{mode},
		"timestamp":    []string{fmt.Sprintf("%d", time.Now().Unix())},
	}

	signature := s.generateSignature(params.Encode())
	params.Add("signature", signature)

	return fmt.Sprintf("%s/launch?%s", s.config.APIURL, params.Encode()), nil
}

func (s *VeliPlayService) HandleCallback(ctx context.Context, callback *domain.VeliCallback) error {
	if !s.verifyCallbackSignature(callback) {
		return errors.New("invalid callback signature")
	}

	// Veli uses round_id instead of transaction_id for idempotency
	existing, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, callback.RoundID)
	if err != nil || existing != nil {
		s.logger.Warn("duplicate round detected", "round_id", callback.RoundID)
		return nil
	}

	session, err := s.repo.GetVirtualGameSessionByToken(ctx, callback.SessionID)
	if err != nil {
		return fmt.Errorf("failed to get game session: %w", err)
	}

	// Convert amount based on event type (BET, WIN, etc.)
	amount := convertAmount(callback.Amount, callback.EventType)

	tx := &domain.VirtualGameTransaction{
		SessionID:             session.ID,
		UserID:                session.UserID,
		TransactionType:       callback.EventType, // e.g., "bet_placed", "game_result"
		Amount:                amount,
		Currency:              callback.Currency,
		ExternalTransactionID: callback.RoundID, // Veli uses round_id as the unique identifier
		Status:                "COMPLETED",
		CreatedAt:             time.Now(),
		UpdatedAt:             time.Now(),
		GameSpecificData: domain.GameSpecificData{
			Multiplier: callback.Multiplier, // Used for Aviator/Plinko
		},
	}

	if err := s.processTransaction(ctx, tx, session.UserID); err != nil {
		return fmt.Errorf("failed to process transaction: %w", err)
	}

	return nil
}

func (s *VeliPlayService) generateSignature(data string) string {
	h := hmac.New(sha256.New, []byte(s.config.SecretKey))
	h.Write([]byte(data))
	return hex.EncodeToString(h.Sum(nil))
}

func (s *VeliPlayService) verifyCallbackSignature(cb *domain.VeliCallback) bool {
	signData := fmt.Sprintf("%s%s%s%.2f%s%d",
		cb.RoundID, // Veli uses round_id instead of transaction_id
		cb.SessionID,
		cb.EventType,
		cb.Amount,
		cb.Currency,
		cb.Timestamp,
	)
	expectedSig := s.generateSignature(signData)
	return expectedSig == cb.Signature
}

func convertAmount(amount float64, eventType string) int64 {
	cents := int64(amount * 100)
	if eventType == "bet_placed" {
		return -cents // Debit for bets
	}
	return cents // Credit for wins/results
}

func generateSessionToken(userID int64) string {
	return fmt.Sprintf("veli-%d-%d", userID, time.Now().UnixNano())
}

func (s *VeliPlayService) processTransaction(ctx context.Context, tx *domain.VirtualGameTransaction, userID int64) error {
	wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
	if err != nil || len(wallets) == 0 {
		return errors.New("no wallet available for user")
	}
	tx.WalletID = wallets[0].ID

	if err := s.walletSvc.AddToWallet(ctx, tx.WalletID, domain.Currency(tx.Amount)); err != nil {
		return fmt.Errorf("wallet update failed: %w", err)
	}

	return s.repo.CreateVirtualGameTransaction(ctx, tx)
}
