package repository

import (
	"context"
	"fmt"
	"log/slog"
	"time"

	// "fmt"

	dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
	"github.com/jackc/pgx/v5/pgtype"
	"go.uber.org/zap"
)

var (
	logger      *slog.Logger
	mongoLogger *zap.Logger
)

func convertDBBet(bet dbgen.Bet) domain.Bet {
	return domain.Bet{
		ID:          bet.ID,
		Amount:      domain.Currency(bet.Amount),
		TotalOdds:   bet.TotalOdds,
		Status:      domain.OutcomeStatus(bet.Status),
		FullName:    bet.FullName,
		PhoneNumber: bet.PhoneNumber,
		BranchID: domain.ValidInt64{
			Value: bet.BranchID.Int64,
			Valid: bet.BranchID.Valid,
		},
		CompanyID: domain.ValidInt64{
			Value: bet.CompanyID.Int64,
			Valid: bet.CompanyID.Valid,
		},
		UserID: domain.ValidInt64{
			Value: bet.UserID.Int64,
			Valid: bet.UserID.Valid,
		},
		IsShopBet: bet.IsShopBet,
		CashedOut: bet.CashedOut,
		CashoutID: bet.CashoutID,
		CreatedAt: bet.CreatedAt.Time,
	}
}

func convertDBBetOutcomes(outcome dbgen.BetOutcome) domain.BetOutcome {
	return domain.BetOutcome{
		ID:           outcome.ID,
		BetID:        outcome.BetID,
		SportID:      outcome.SportID,
		EventID:      outcome.EventID,
		OddID:        outcome.OddID,
		HomeTeamName: outcome.HomeTeamName,
		AwayTeamName: outcome.AwayTeamName,
		MarketID:     outcome.MarketID,
		MarketName:   outcome.MarketName,
		Odd:          outcome.Odd,
		OddName:      outcome.OddName,
		OddHeader:    outcome.OddHeader,
		OddHandicap:  outcome.OddHandicap,
		Status:       domain.OutcomeStatus(outcome.Status),
		Expires:      outcome.Expires.Time,
	}
}

func convertDBBetWithOutcomes(bet dbgen.BetWithOutcome) domain.GetBet {
	var outcomes []domain.BetOutcome = make([]domain.BetOutcome, 0, len(bet.Outcomes))

	for _, outcome := range bet.Outcomes {
		outcomes = append(outcomes, convertDBBetOutcomes(outcome))
	}

	return domain.GetBet{
		ID:          bet.ID,
		Amount:      domain.Currency(bet.Amount),
		TotalOdds:   bet.TotalOdds,
		Status:      domain.OutcomeStatus(bet.Status),
		FullName:    bet.FullName,
		PhoneNumber: bet.PhoneNumber,
		BranchID: domain.ValidInt64{
			Value: bet.BranchID.Int64,
			Valid: bet.BranchID.Valid,
		},
		UserID: domain.ValidInt64{
			Value: bet.UserID.Int64,
			Valid: bet.UserID.Valid,
		},
		IsShopBet: bet.IsShopBet,
		CashedOut: bet.CashedOut,
		CashoutID: bet.CashoutID,
		Outcomes:  outcomes,
		CreatedAt: bet.CreatedAt.Time,
	}
}

func convertDBCreateBetOutcome(betOutcome domain.CreateBetOutcome) dbgen.CreateBetOutcomeParams {
	return dbgen.CreateBetOutcomeParams{
		BetID:        betOutcome.BetID,
		EventID:      betOutcome.EventID,
		SportID:      betOutcome.SportID,
		OddID:        betOutcome.OddID,
		HomeTeamName: betOutcome.HomeTeamName,
		AwayTeamName: betOutcome.AwayTeamName,
		MarketID:     betOutcome.MarketID,
		MarketName:   betOutcome.MarketName,
		Odd:          betOutcome.Odd,
		OddName:      betOutcome.OddName,
		OddHeader:    betOutcome.OddHeader,
		OddHandicap:  betOutcome.OddHandicap,
		Expires: pgtype.Timestamp{
			Time:  betOutcome.Expires,
			Valid: true,
		},
	}
}

func convertCreateBet(bet domain.CreateBet) dbgen.CreateBetParams {
	return dbgen.CreateBetParams{
		Amount:      int64(bet.Amount),
		TotalOdds:   bet.TotalOdds,
		Status:      int32(bet.Status),
		FullName:    bet.FullName,
		PhoneNumber: bet.PhoneNumber,

		BranchID: pgtype.Int8{
			Int64: bet.BranchID.Value,
			Valid: bet.BranchID.Valid,
		},
		UserID: pgtype.Int8{
			Int64: bet.UserID.Value,
			Valid: bet.UserID.Valid,
		},
		IsShopBet: bet.IsShopBet,
		CashoutID: bet.CashoutID,
	}
}

func (s *Store) CreateBet(ctx context.Context, bet domain.CreateBet) (domain.Bet, error) {
	newBet, err := s.queries.CreateBet(ctx, convertCreateBet(bet))
	if err != nil {
		fmt.Println("We are here")
		logger.Error("Failed to create bet", slog.String("error", err.Error()), slog.Any("bet", bet))
		return domain.Bet{}, err
	}
	return convertDBBet(newBet), err

}

func (s *Store) CreateBetOutcome(ctx context.Context, outcomes []domain.CreateBetOutcome) (int64, error) {
	var dbParams []dbgen.CreateBetOutcomeParams = make([]dbgen.CreateBetOutcomeParams, 0, len(outcomes))

	for _, outcome := range outcomes {
		dbParams = append(dbParams, convertDBCreateBetOutcome(outcome))
	}

	rows, err := s.queries.CreateBetOutcome(ctx, dbParams)
	if err != nil {
		domain.MongoDBLogger.Error("failed to create bet outcomes in DB",
			zap.Int("outcome_count", len(outcomes)),
			zap.Any("bet_id", outcomes[0].BetID), // assumes all outcomes have same BetID
			zap.Error(err),
		)
		return rows, err
	}

	return rows, nil
}

func (s *Store) GetBetByID(ctx context.Context, id int64) (domain.GetBet, error) {
	bet, err := s.queries.GetBetByID(ctx, id)
	if err != nil {
		domain.MongoDBLogger.Error("failed to get bet by ID",
			zap.Int64("bet_id", id),
			zap.Error(err),
		)
		return domain.GetBet{}, err
	}

	return convertDBBetWithOutcomes(bet), nil
}

func (s *Store) GetBetByCashoutID(ctx context.Context, id string) (domain.GetBet, error) {
	bet, err := s.queries.GetBetByCashoutID(ctx, id)
	if err != nil {
		domain.MongoDBLogger.Error("failed to get bet by cashout ID",
			zap.String("cashout_id", id),
			zap.Error(err),
		)
		return domain.GetBet{}, err
	}

	return convertDBBetWithOutcomes(bet), nil
}

func (s *Store) GetAllBets(ctx context.Context, filter domain.BetFilter) ([]domain.GetBet, error) {
	bets, err := s.queries.GetAllBets(ctx, dbgen.GetAllBetsParams{
		BranchID: pgtype.Int8{
			Int64: filter.BranchID.Value,
			Valid: filter.BranchID.Valid,
		},
		CompanyID: pgtype.Int8{
			Int64: filter.CompanyID.Value,
			Valid: filter.CompanyID.Valid,
		},
		UserID: pgtype.Int8{
			Int64: filter.UserID.Value,
			Valid: filter.UserID.Valid,
		},
	})
	if err != nil {
		domain.MongoDBLogger.Error("failed to get all bets",
			zap.Any("filter", filter),
			zap.Error(err),
		)
		return nil, err
	}

	var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
	for _, bet := range bets {
		result = append(result, convertDBBetWithOutcomes(bet))
	}

	return result, nil
}

func (s *Store) GetBetByBranchID(ctx context.Context, BranchID int64) ([]domain.GetBet, error) {
	bets, err := s.queries.GetBetByBranchID(ctx, pgtype.Int8{
		Int64: BranchID,
		Valid: true,
	})
	if err != nil {
		domain.MongoDBLogger.Error("failed to get bets by branch ID",
			zap.Int64("branch_id", BranchID),
			zap.Error(err),
		)
		return nil, err
	}

	var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
	for _, bet := range bets {
		result = append(result, convertDBBetWithOutcomes(bet))
	}

	return result, nil
}

func (s *Store) GetBetByUserID(ctx context.Context, UserID int64) ([]domain.GetBet, error) {
	bets, err := s.queries.GetBetByUserID(ctx, pgtype.Int8{
		Int64: UserID,
		Valid: true,
	})

	if err != nil {
		return nil, err
	}

	var result []domain.GetBet = make([]domain.GetBet, 0, len(bets))
	for _, bet := range bets {
		result = append(result, convertDBBetWithOutcomes(bet))
	}

	return result, nil
}

func (s *Store) GetBetCount(ctx context.Context, UserID int64, outcomesHash string) (int64, error) {
	count, err := s.queries.GetBetCount(ctx, dbgen.GetBetCountParams{
		UserID:       pgtype.Int8{Int64: UserID},
		OutcomesHash: outcomesHash,
	})

	if err != nil {
		return 0, err
	}

	return count, nil
}

func (s *Store) UpdateCashOut(ctx context.Context, id int64, cashedOut bool) error {
	err := s.queries.UpdateCashOut(ctx, dbgen.UpdateCashOutParams{
		ID:        id,
		CashedOut: cashedOut,
	})
	if err != nil {
		domain.MongoDBLogger.Error("failed to update cashout",
			zap.Int64("id", id),
			zap.Bool("cashed_out", cashedOut),
			zap.Error(err),
		)
	}
	return err
}

func (s *Store) UpdateStatus(ctx context.Context, id int64, status domain.OutcomeStatus) error {
	err := s.queries.UpdateStatus(ctx, dbgen.UpdateStatusParams{
		ID:     id,
		Status: int32(status),
	})
	if err != nil {
		domain.MongoDBLogger.Error("failed to update status",
			zap.Int64("id", id),
			zap.Int32("status", int32(status)),
			zap.Error(err),
		)
	}
	return err
}

func (s *Store) GetBetOutcomeByEventID(ctx context.Context, eventID int64) ([]domain.BetOutcome, error) {
	outcomes, err := s.queries.GetBetOutcomeByEventID(ctx, eventID)
	if err != nil {
		domain.MongoDBLogger.Error("failed to get bet outcomes by event ID",
			zap.Int64("event_id", eventID),
			zap.Error(err),
		)
		return nil, err
	}

	var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
	for _, outcome := range outcomes {
		result = append(result, convertDBBetOutcomes(outcome))
	}
	return result, nil
}

func (s *Store) GetBetOutcomeByBetID(ctx context.Context, betID int64) ([]domain.BetOutcome, error) {
	outcomes, err := s.queries.GetBetOutcomeByBetID(ctx, betID)
	if err != nil {
		domain.MongoDBLogger.Error("failed to get bet outcomes by bet ID",
			zap.Int64("bet_id", betID),
			zap.Error(err),
		)
		return nil, err
	}

	var result []domain.BetOutcome = make([]domain.BetOutcome, 0, len(outcomes))
	for _, outcome := range outcomes {
		result = append(result, convertDBBetOutcomes(outcome))
	}
	return result, nil
}

func (s *Store) UpdateBetOutcomeStatus(ctx context.Context, id int64, status domain.OutcomeStatus) (domain.BetOutcome, error) {
	update, err := s.queries.UpdateBetOutcomeStatus(ctx, dbgen.UpdateBetOutcomeStatusParams{
		Status: int32(status),
		ID:     id,
	})
	if err != nil {
		domain.MongoDBLogger.Error("failed to update bet outcome status",
			zap.Int64("id", id),
			zap.Int32("status", int32(status)),
			zap.Error(err),
		)
		return domain.BetOutcome{}, err
	}

	res := convertDBBetOutcomes(update)
	return res, nil
}

func (s *Store) DeleteBet(ctx context.Context, id int64) error {
	return s.queries.DeleteBet(ctx, id)
}

// GetBetSummary returns aggregated bet statistics
func (s *Store) GetBetSummary(ctx context.Context, filter domain.ReportFilter) (
	totalStakes domain.Currency,
	totalBets int64,
	activeBets int64,
	totalWins int64,
	totalLosses int64,
	winBalance domain.Currency,
	err error,
) {
	query := `SELECT
        COALESCE(SUM(amount), 0) as total_stakes,
        COALESCE(COUNT(*), 0) as total_bets,
        COALESCE(SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END), 0) as active_bets,
        COALESCE(SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END), 0) as total_wins,
        COALESCE(SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END), 0) as total_losses,
        COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as win_balance
        FROM bets`

	args := []interface{}{}
	argPos := 1

	// Add filters if provided
	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND %screated_at >= $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND %sstatus = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	row := s.conn.QueryRow(ctx, query, args...)
	err = row.Scan(&totalStakes, &totalBets, &activeBets, &totalWins, &totalLosses, &winBalance)
	if err != nil {
		domain.MongoDBLogger.Error("failed to get bet summary",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return 0, 0, 0, 0, 0, 0, fmt.Errorf("failed to get bet summary: %w", err)
	}

	domain.MongoDBLogger.Info("GetBetSummary executed successfully",
		zap.String("query", query),
		zap.Any("args", args),
		zap.Float64("totalStakes", float64(totalStakes)), // convert if needed
		zap.Int64("totalBets", totalBets),
		zap.Int64("activeBets", activeBets),
		zap.Int64("totalWins", totalWins),
		zap.Int64("totalLosses", totalLosses),
		zap.Float64("winBalance", float64(winBalance)), // convert if needed
	)
	return totalStakes, totalBets, activeBets, totalWins, totalLosses, winBalance, nil
}

// GetBetStats returns bet statistics grouped by date
func (s *Store) GetBetStats(ctx context.Context, filter domain.ReportFilter) ([]domain.BetStat, error) {
	query := `SELECT
        DATE(created_at) as date,
        COUNT(*) as total_bets,
        COALESCE(SUM(amount), 0) as total_stakes,
        SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as total_wins,
        COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as total_payouts,
        AVG(total_odds) as average_odds
        FROM bets`

	args := []interface{}{}
	argPos := 1

	// Add filters if provided
	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND %screated_at >= $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND %sstatus = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += " GROUP BY DATE(created_at) ORDER BY DATE(created_at)"

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query bet stats",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query bet stats: %w", err)
	}
	defer rows.Close()

	var stats []domain.BetStat
	for rows.Next() {
		var stat domain.BetStat
		if err := rows.Scan(
			&stat.Date,
			&stat.TotalBets,
			&stat.TotalStakes,
			&stat.TotalWins,
			&stat.TotalPayouts,
			&stat.AverageOdds,
		); err != nil {
			domain.MongoDBLogger.Error("failed to scan bet stat",
				zap.Error(err),
			)
			return nil, fmt.Errorf("failed to scan bet stat: %w", err)
		}
		stats = append(stats, stat)
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration",
			zap.Error(err),
		)
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetBetStats executed successfully",
		zap.Int("result_count", len(stats)),
		zap.String("query", query),
		zap.Any("args", args),
	)
	return stats, nil
}

// GetSportPopularity returns the most popular sport by date
func (s *Store) GetSportPopularity(ctx context.Context, filter domain.ReportFilter) (map[time.Time]string, error) {
	query := `WITH sport_counts AS (
        SELECT 
            DATE(b.created_at) as date,
            bo.sport_id,
            COUNT(*) as bet_count,
            ROW_NUMBER() OVER (PARTITION BY DATE(b.created_at) ORDER BY COUNT(*) DESC) as rank
        FROM bets b
        JOIN bet_outcomes bo ON b.id = bo.bet_id
        WHERE bo.sport_id IS NOT NULL`

	args := []interface{}{}
	argPos := 1

	// Add filters if provided
	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND b.status = $%d", argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += ` GROUP BY DATE(b.created_at), bo.sport_id
    )
    SELECT date, sport_id FROM sport_counts WHERE rank = 1`

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query sport popularity",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query sport popularity: %w", err)
	}
	defer rows.Close()

	popularity := make(map[time.Time]string)
	for rows.Next() {
		var date time.Time
		var sportID string
		if err := rows.Scan(&date, &sportID); err != nil {
			domain.MongoDBLogger.Error("failed to scan sport popularity",
				zap.Error(err),
			)
			return nil, fmt.Errorf("failed to scan sport popularity: %w", err)
		}
		popularity[date] = sportID
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration",
			zap.Error(err),
		)
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetSportPopularity executed successfully",
		zap.Int("result_count", len(popularity)),
		zap.String("query", query),
		zap.Any("args", args),
	)
	return popularity, nil
}

// GetMarketPopularity returns the most popular market by date
func (s *Store) GetMarketPopularity(ctx context.Context, filter domain.ReportFilter) (map[time.Time]string, error) {
	query := `WITH market_counts AS (
        SELECT 
            DATE(b.created_at) as date,
            bo.market_name,
            COUNT(*) as bet_count,
            ROW_NUMBER() OVER (PARTITION BY DATE(b.created_at) ORDER BY COUNT(*) DESC) as rank
        FROM bets b
        JOIN bet_outcomes bo ON b.id = bo.bet_id
        WHERE bo.market_name IS NOT NULL`

	args := []interface{}{}
	argPos := 1

	// Add filters if provided
	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND b.status = $%d", argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += ` GROUP BY DATE(b.created_at), bo.market_name
    )
    SELECT date, market_name FROM market_counts WHERE rank = 1`

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query market popularity",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query market popularity: %w", err)
	}
	defer rows.Close()

	popularity := make(map[time.Time]string)
	for rows.Next() {
		var date time.Time
		var marketName string
		if err := rows.Scan(&date, &marketName); err != nil {
			domain.MongoDBLogger.Error("failed to scan market popularity",
				zap.Error(err),
			)
			return nil, fmt.Errorf("failed to scan market popularity: %w", err)
		}
		popularity[date] = marketName
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration",
			zap.Error(err),
		)
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetMarketPopularity executed successfully",
		zap.Int("result_count", len(popularity)),
		zap.String("query", query),
		zap.Any("args", args),
	)
	return popularity, nil
}

// GetExtremeValues returns the highest stake and payout by date
func (s *Store) GetExtremeValues(ctx context.Context, filter domain.ReportFilter) (map[time.Time]domain.ExtremeValues, error) {
	query := `SELECT
        DATE(created_at) as date,
        MAX(amount) as highest_stake,
        MAX(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END) as highest_payout
        FROM bets`

	args := []interface{}{}
	argPos := 1

	// Add filters if provided
	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" WHERE company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND %sbranch_id = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND %suser_id = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND %screated_at >= $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND %sstatus = $%d", func() string {
			if len(args) == 0 {
				return " WHERE "
			}
			return " AND "
		}(), argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += " GROUP BY DATE(created_at)"

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query extreme values",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query extreme values: %w", err)
	}
	defer rows.Close()

	extremes := make(map[time.Time]domain.ExtremeValues)
	for rows.Next() {
		var date time.Time
		var extreme domain.ExtremeValues
		if err := rows.Scan(&date, &extreme.HighestStake, &extreme.HighestPayout); err != nil {
			domain.MongoDBLogger.Error("failed to scan extreme values",
				zap.Error(err),
			)
			return nil, fmt.Errorf("failed to scan extreme values: %w", err)
		}
		extremes[date] = extreme
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration",
			zap.Error(err),
		)
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetExtremeValues executed successfully",
		zap.Int("result_count", len(extremes)),
		zap.String("query", query),
		zap.Any("args", args),
	)
	return extremes, nil
}

// GetCustomerBetActivity returns bet activity by customer
func (s *Store) GetCustomerBetActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.CustomerBetActivity, error) {
	query := `SELECT
        user_id as customer_id,
        COUNT(*) as total_bets,
        COALESCE(SUM(amount), 0) as total_stakes,
        SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as total_wins,
        COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as total_payouts,
        MIN(created_at) as first_bet_date,
        MAX(created_at) as last_bet_date,
        AVG(total_odds) as average_odds
        FROM bets
        WHERE user_id IS NOT NULL`

	args := []interface{}{}
	argPos := 1

	// Add filters if provided
	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" AND company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND branch_id = $%d", argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND user_id = $%d", argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND created_at >= $%d", argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND status = $%d", argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += " GROUP BY user_id"

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query customer bet activity",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query customer bet activity: %w", err)
	}
	defer rows.Close()

	var activities []domain.CustomerBetActivity
	for rows.Next() {
		var activity domain.CustomerBetActivity
		if err := rows.Scan(
			&activity.CustomerID,
			&activity.TotalBets,
			&activity.TotalStakes,
			&activity.TotalWins,
			&activity.TotalPayouts,
			&activity.FirstBetDate,
			&activity.LastBetDate,
			&activity.AverageOdds,
		); err != nil {
			domain.MongoDBLogger.Error("failed to scan customer bet activity",
				zap.Error(err),
			)
			return nil, fmt.Errorf("failed to scan customer bet activity: %w", err)
		}
		activities = append(activities, activity)
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration",
			zap.Error(err),
		)
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetCustomerBetActivity executed successfully",
		zap.Int("result_count", len(activities)),
		zap.String("query", query),
		zap.Any("args", args),
	)
	return activities, nil
}

// GetBranchBetActivity returns bet activity by branch
func (s *Store) GetBranchBetActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.BranchBetActivity, error) {
	query := `SELECT
        branch_id,
        COUNT(*) as total_bets,
        COALESCE(SUM(amount), 0) as total_stakes,
        SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as total_wins,
        COALESCE(SUM(CASE WHEN status = 1 THEN amount * total_odds ELSE 0 END), 0) as total_payouts
        FROM bets
        WHERE branch_id IS NOT NULL`

	args := []interface{}{}
	argPos := 1

	// Add filters if provided
	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" AND company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND branch_id = $%d", argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND created_at >= $%d", argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND status = $%d", argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += " GROUP BY branch_id"

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query branch bet activity",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query branch bet activity: %w", err)
	}
	defer rows.Close()

	var activities []domain.BranchBetActivity
	for rows.Next() {
		var activity domain.BranchBetActivity
		if err := rows.Scan(
			&activity.BranchID,
			&activity.TotalBets,
			&activity.TotalStakes,
			&activity.TotalWins,
			&activity.TotalPayouts,
		); err != nil {
			domain.MongoDBLogger.Error("failed to scan branch bet activity", zap.Error(err))
			return nil, fmt.Errorf("failed to scan branch bet activity: %w", err)
		}
		activities = append(activities, activity)
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetBranchBetActivity executed successfully",
		zap.Int("result_count", len(activities)),
		zap.String("query", query),
		zap.Any("args", args),
	)
	return activities, nil
}

// GetSportBetActivity returns bet activity by sport
func (s *Store) GetSportBetActivity(ctx context.Context, filter domain.ReportFilter) ([]domain.SportBetActivity, error) {
	query := `SELECT
        bo.sport_id,
        COUNT(*) as total_bets,
        COALESCE(SUM(b.amount), 0) as total_stakes,
        SUM(CASE WHEN b.status = 1 THEN 1 ELSE 0 END) as total_wins,
        COALESCE(SUM(CASE WHEN b.status = 1 THEN b.amount * b.total_odds ELSE 0 END), 0) as total_payouts,
        AVG(b.total_odds) as average_odds
        FROM bets b
        JOIN bet_outcomes bo ON b.id = bo.bet_id
        WHERE bo.sport_id IS NOT NULL`

	args := []interface{}{}
	argPos := 1

	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND b.status = $%d", argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += " GROUP BY bo.sport_id"

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query sport bet activity",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query sport bet activity: %w", err)
	}
	defer rows.Close()

	var activities []domain.SportBetActivity
	for rows.Next() {
		var activity domain.SportBetActivity
		if err := rows.Scan(
			&activity.SportID,
			&activity.TotalBets,
			&activity.TotalStakes,
			&activity.TotalWins,
			&activity.TotalPayouts,
			&activity.AverageOdds,
		); err != nil {
			domain.MongoDBLogger.Error("failed to scan sport bet activity", zap.Error(err))
			return nil, fmt.Errorf("failed to scan sport bet activity: %w", err)
		}
		activities = append(activities, activity)
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetSportBetActivity executed successfully",
		zap.Int("result_count", len(activities)),
		zap.String("query", query),
		zap.Any("args", args),
	)
	return activities, nil
}

// GetSportDetails returns sport names by ID
func (s *Store) GetSportDetails(ctx context.Context, filter domain.ReportFilter) (map[string]string, error) {
	query := `SELECT DISTINCT bo.sport_id, e.match_name
              FROM bet_outcomes bo
              JOIN events e ON bo.event_id = e.id::bigint
              JOIN bets b ON b.id = bo.bet_id
              WHERE bo.sport_id IS NOT NULL`

	args := []interface{}{}
	argPos := 1

	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND bo.created_at >= $%d", argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND bo.created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query sport details",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query sport details: %w", err)
	}
	defer rows.Close()

	details := make(map[string]string)
	for rows.Next() {
		var sportID, matchName string
		if err := rows.Scan(&sportID, &matchName); err != nil {
			domain.MongoDBLogger.Error("failed to scan sport detail", zap.Error(err))
			return nil, fmt.Errorf("failed to scan sport detail: %w", err)
		}
		details[sportID] = matchName
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetSportDetails executed successfully",
		zap.Int("result_count", len(details)),
		zap.String("query", query),
		zap.Any("args", args),
	)

	return details, nil
}

// GetSportMarketPopularity returns most popular market by sport
func (s *Store) GetSportMarketPopularity(ctx context.Context, filter domain.ReportFilter) (map[string]string, error) {
	query := `WITH market_counts AS (
        SELECT 
            bo.sport_id,
            bo.market_name,
            COUNT(*) AS bet_count,
            ROW_NUMBER() OVER (PARTITION BY bo.sport_id ORDER BY COUNT(*) DESC) as rank
        FROM bets b
        JOIN bet_outcomes bo ON b.id = bo.bet_id
        WHERE bo.sport_id IS NOT NULL AND bo.market_name IS NOT NULL`

	args := []interface{}{}
	argPos := 1

	if filter.CompanyID.Valid {
		query += fmt.Sprintf(" AND b.company_id = $%d", argPos)
		args = append(args, filter.CompanyID.Value)
		argPos++
	}
	if filter.BranchID.Valid {
		query += fmt.Sprintf(" AND b.branch_id = $%d", argPos)
		args = append(args, filter.BranchID.Value)
		argPos++
	}
	if filter.UserID.Valid {
		query += fmt.Sprintf(" AND b.user_id = $%d", argPos)
		args = append(args, filter.UserID.Value)
		argPos++
	}
	if filter.StartTime.Valid {
		query += fmt.Sprintf(" AND b.created_at >= $%d", argPos)
		args = append(args, filter.StartTime.Value)
		argPos++
	}
	if filter.EndTime.Valid {
		query += fmt.Sprintf(" AND b.created_at <= $%d", argPos)
		args = append(args, filter.EndTime.Value)
		argPos++
	}
	if filter.Status.Valid {
		query += fmt.Sprintf(" AND b.status = $%d", argPos)
		args = append(args, filter.Status.Value)
		argPos++
	}

	query += ` GROUP BY bo.sport_id, bo.market_name
    )
    SELECT sport_id, market_name FROM market_counts WHERE rank = 1`

	rows, err := s.conn.Query(ctx, query, args...)
	if err != nil {
		domain.MongoDBLogger.Error("failed to query sport market popularity",
			zap.String("query", query),
			zap.Any("args", args),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to query sport market popularity: %w", err)
	}
	defer rows.Close()

	popularity := make(map[string]string)
	for rows.Next() {
		var sportID, marketName string
		if err := rows.Scan(&sportID, &marketName); err != nil {
			domain.MongoDBLogger.Error("failed to scan sport market popularity", zap.Error(err))
			return nil, fmt.Errorf("failed to scan sport market popularity: %w", err)
		}
		popularity[sportID] = marketName
	}

	if err = rows.Err(); err != nil {
		domain.MongoDBLogger.Error("rows error after iteration", zap.Error(err))
		return nil, fmt.Errorf("rows error: %w", err)
	}

	domain.MongoDBLogger.Info("GetSportMarketPopularity executed successfully",
		zap.Int("result_count", len(popularity)),
		zap.String("query", query),
		zap.Any("args", args),
	)

	return popularity, nil
}
