package repository

import (
	"context"
	"encoding/json"

	"os"
	"strconv"
	"time"

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

func (s *Store) SaveNonLiveMarket(ctx context.Context, m domain.Market) error {
	if len(m.Odds) == 0 {
		return nil
	}

	for _, item := range m.Odds {
		var name string
		var oddsVal float64

		if m.Source == "bwin" {
			nameValue := getMap(item["name"])
			name = getString(nameValue["value"])
			oddsVal = getFloat(item["odds"])
		} else {
			name = getString(item["name"])
			oddsVal = getConvertedFloat(item["odds"])
		}
		handicap := getString(item["handicap"])

		rawOddsBytes, _ := json.Marshal(m.Odds)

		params := dbgen.InsertNonLiveOddParams{
			EventID:        pgtype.Text{String: m.EventID, Valid: m.EventID != ""},
			Fi:             pgtype.Text{String: m.FI, Valid: m.FI != ""},
			MarketType:     m.MarketType,
			MarketName:     pgtype.Text{String: m.MarketName, Valid: m.MarketName != ""},
			MarketCategory: pgtype.Text{String: m.MarketCategory, Valid: m.MarketCategory != ""},
			MarketID:       pgtype.Text{String: m.MarketID, Valid: m.MarketID != ""},
			Name:           pgtype.Text{String: name, Valid: name != ""},
			Handicap:       pgtype.Text{String: handicap, Valid: handicap != ""},
			OddsValue:      pgtype.Float8{Float64: oddsVal, Valid: oddsVal != 0},
			Section:        m.MarketCategory,
			Category:       pgtype.Text{Valid: false},
			RawOdds:        rawOddsBytes,
			IsActive:       pgtype.Bool{Bool: true, Valid: true},
			Source:         pgtype.Text{String: m.Source, Valid: true},
			FetchedAt:      pgtype.Timestamp{Time: time.Now(), Valid: true},
		}

		err := s.queries.InsertNonLiveOdd(ctx, params)
		if err != nil {
			_ = writeFailedMarketLog(m, err)
			continue
		}
	}
	return nil
}

func writeFailedMarketLog(m domain.Market, err error) error {
	logDir := "logs"
	logFile := logDir + "/failed_markets.log"

	if mkErr := os.MkdirAll(logDir, 0755); mkErr != nil {
		return mkErr
	}

	f, fileErr := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if fileErr != nil {
		return fileErr
	}
	defer f.Close()

	entry := struct {
		Time   string        `json:"time"`
		Error  string        `json:"error"`
		Record domain.Market `json:"record"`
	}{
		Time:   time.Now().Format(time.RFC3339),
		Error:  err.Error(),
		Record: m,
	}

	jsonData, _ := json.MarshalIndent(entry, "", "  ")
	_, writeErr := f.WriteString(string(jsonData) + "\n\n")
	return writeErr
}

func (s *Store) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
	odds, err := s.queries.GetPrematchOdds(ctx)
	if err != nil {
		return nil, err
	}

	domainOdds := make([]domain.Odd, len(odds))
	for i, odd := range odds {
		domainOdds[i] = domain.Odd{
			EventID:        odd.EventID.String,
			Fi:             odd.Fi.String,
			MarketType:     odd.MarketType,
			MarketName:     odd.MarketName.String,
			MarketCategory: odd.MarketCategory.String,
			MarketID:       odd.MarketID.String,
			Name:           odd.Name.String,
			Handicap:       odd.Handicap.String,
			OddsValue:      odd.OddsValue.Float64,
			Section:        odd.Section,
			Category:       odd.Category.String,
			RawOdds: func() []domain.RawMessage {
				var rawOdds []domain.RawMessage
				if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil {
					rawOdds = nil
				}
				return rawOdds
			}(),
			FetchedAt: odd.FetchedAt.Time,
			Source:    odd.Source.String,
			IsActive:  odd.IsActive.Bool,
		}
	}

	return domainOdds, nil
}

func (s *Store) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) {
	rows, err := s.queries.GetALLPrematchOdds(ctx)
	if err != nil {
		return nil, err
	}

	domainOdds := make([]domain.Odd, len(rows))
	for i, row := range rows {
		domainOdds[i] = domain.Odd{
			// ID:             int64(row.ID),
			EventID:        row.EventID.String,
			Fi:             row.Fi.String,
			MarketType:     row.MarketType,
			MarketName:     row.MarketName.String,
			MarketCategory: row.MarketCategory.String,
			MarketID:       row.MarketID.String,
			Name:           row.Name.String,
			Handicap:       row.Handicap.String,
			OddsValue:      row.OddsValue.Float64,
			Section:        row.Section,
			Category:       row.Category.String,
			RawOdds: func() []domain.RawMessage {
				var rawOdds []domain.RawMessage
				if err := json.Unmarshal(row.RawOdds, &rawOdds); err != nil {
					rawOdds = nil
				}
				return rawOdds
			}(),
			FetchedAt: row.FetchedAt.Time,
			Source:    row.Source.String,
			IsActive:  row.IsActive.Bool,
		}
	}

	return domainOdds, nil
}

func (s *Store) GetRawOddsByMarketID(ctx context.Context, rawOddsID string, upcomingID string) (domain.RawOddsByMarketID, error) {
	params := dbgen.GetRawOddsByMarketIDParams{
		MarketID: pgtype.Text{String: rawOddsID, Valid: true},
		Fi:       pgtype.Text{String: upcomingID, Valid: true},
	}

	odds, err := s.queries.GetRawOddsByMarketID(ctx, params)
	if err != nil {
		return domain.RawOddsByMarketID{}, err
	}

	var rawOdds []json.RawMessage
	if err := json.Unmarshal(odds.RawOdds, &rawOdds); err != nil {
		return domain.RawOddsByMarketID{}, err
	}

	return domain.RawOddsByMarketID{
		ID:         int64(odds.ID),
		MarketName: odds.MarketName.String,
		Handicap:   odds.Handicap.String,
		RawOdds: func() []domain.RawMessage {
			converted := make([]domain.RawMessage, len(rawOdds))
			for i, r := range rawOdds {
				converted[i] = domain.RawMessage(r)
			}
			return converted
		}(),
		FetchedAt: odds.FetchedAt.Time,
	}, nil
}
func (s *Store) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit domain.ValidInt64, offset domain.ValidInt64) ([]domain.Odd, error) {
	odds, err := s.queries.GetPaginatedPrematchOddsByUpcomingID(ctx, dbgen.GetPaginatedPrematchOddsByUpcomingIDParams{
		ID: upcomingID,
		Limit: pgtype.Int4{
			Int32: int32(limit.Value),
			Valid: limit.Valid,
		},
		Offset: pgtype.Int4{
			Int32: int32(offset.Value),
			Valid: offset.Valid,
		},
	})
	if err != nil {
		return nil, err
	}
	// Map the results to domain.Odd
	domainOdds := make([]domain.Odd, len(odds))
	for i, odd := range odds {
		var rawOdds []domain.RawMessage
		if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil {
			rawOdds = nil
		}

		domainOdds[i] = domain.Odd{
			EventID:        odd.EventID.String,
			Fi:             odd.Fi.String,
			MarketType:     odd.MarketType,
			MarketName:     odd.MarketName.String,
			MarketCategory: odd.MarketCategory.String,
			MarketID:       odd.MarketID.String,
			Name:           odd.Name.String,
			Handicap:       odd.Handicap.String,
			OddsValue:      odd.OddsValue.Float64,
			Section:        odd.Section,
			Category:       odd.Category.String,
			RawOdds:        rawOdds,
			FetchedAt:      odd.FetchedAt.Time,
			Source:         odd.Source.String,
			IsActive:       odd.IsActive.Bool,
		}
	}

	return domainOdds, nil
}

func (s *Store) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {

	odds, err := s.queries.GetPrematchOddsByUpcomingID(ctx, upcomingID)
	if err != nil {
		return nil, err
	}

	// Map the results to domain.Odd
	domainOdds := make([]domain.Odd, len(odds))
	for i, odd := range odds {
		var rawOdds []domain.RawMessage
		if err := json.Unmarshal(odd.RawOdds, &rawOdds); err != nil {
			rawOdds = nil
		}

		domainOdds[i] = domain.Odd{
			EventID:        odd.EventID.String,
			Fi:             odd.Fi.String,
			MarketType:     odd.MarketType,
			MarketName:     odd.MarketName.String,
			MarketCategory: odd.MarketCategory.String,
			MarketID:       odd.MarketID.String,
			Name:           odd.Name.String,
			Handicap:       odd.Handicap.String,
			OddsValue:      odd.OddsValue.Float64,
			Section:        odd.Section,
			Category:       odd.Category.String,
			RawOdds:        rawOdds,
			FetchedAt:      odd.FetchedAt.Time,
			Source:         odd.Source.String,
			IsActive:       odd.IsActive.Bool,
		}
	}

	return domainOdds, nil
}

func (s *Store) DeleteOddsForEvent(ctx context.Context, eventID string) error {
	return s.queries.DeleteOddsForEvent(ctx, pgtype.Text{
		String: eventID,
		Valid:  true,
	})
}

func getString(v interface{}) string {
	if s, ok := v.(string); ok {
		return s
	}
	return ""
}

func getConvertedFloat(v interface{}) float64 {
	if s, ok := v.(string); ok {
		f, err := strconv.ParseFloat(s, 64)
		if err == nil {
			return f
		}
	}
	return 0
}

func getFloat(v interface{}) float64 {
	if n, ok := v.(float64); ok {
		return n
	}
	return 0
}

func getMap(v interface{}) map[string]interface{} {
	if m, ok := v.(map[string]interface{}); ok {
		return m
	}
	return nil
}
