package odds

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"log/slog"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
)

type ServiceImpl struct {
	store  *repository.Store
	config *config.Config
	logger *slog.Logger
	client *http.Client
}

func New(store *repository.Store, cfg *config.Config, logger *slog.Logger) *ServiceImpl {
	return &ServiceImpl{
		store:  store,
		config: cfg,
		logger: logger,
		client: &http.Client{Timeout: 10 * time.Second},
	}
}

// TODO Add the optimization to get 10 events at the same time
func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error {
	var wg sync.WaitGroup
	errChan := make(chan error, 2)
	// wg.Add(2)
	wg.Add(1)

	go func() {
		defer wg.Done()
		if err := s.fetchBet365Odds(ctx); err != nil {
			errChan <- fmt.Errorf("bet365 odds fetching error: %w", err)
		}
	}()

	// go func() {
	// 	defer wg.Done()
	// 	if err := s.fetchBwinOdds(ctx); err != nil {
	// 		errChan <- fmt.Errorf("bwin odds fetching error: %w", err)
	// 	}
	// }()

	go func() {
		wg.Wait()
		close(errChan)
	}()

	var errs []error
	for err := range errChan {
		errs = append(errs, err)
	}

	if len(errs) > 0 {
		return errors.Join(errs...)
	}

	return nil
}

func (s *ServiceImpl) fetchBet365Odds(ctx context.Context) error {
	eventIDs, err := s.store.GetAllUpcomingEvents(ctx)
	if err != nil {
		log.Printf("❌ Failed to fetch upcoming event IDs: %v", err)
		return err
	}

	var errs []error

	for index, event := range eventIDs {
		log.Printf("📡 Fetching prematch odds for event ID: %v (%d/%d)  ", event.ID, index, len(eventIDs))

		oddsData, err := s.FetchNonLiveOddsByEventID(ctx, event.ID)
		if err != nil {
			s.logger.Error("Failed to fetch prematch odds", "eventID", event.ID, "error", err)
			errs = append(errs, fmt.Errorf("failed to fetch prematch odds for event %v: %w", event.ID, err))
			continue
		}

		parsedOddSections, err := s.ParseOddSections(ctx, oddsData.Results[0], event.SportID)
		if err != nil {
			s.logger.Error("Failed to parse odd section", "error", err)
			errs = append(errs, fmt.Errorf("failed to parse odd section for event %v: %w", event.ID, err))
			continue
		}

		if parsedOddSections.EventFI == "" {
			s.logger.Error("Skipping result with no valid Event FI field", "fi", parsedOddSections.EventFI)
			errs = append(errs, errors.New("event FI is empty"))
			continue
		}

		for oddCategory, section := range parsedOddSections.Sections {
			if err := s.storeSection(ctx, event.ID, parsedOddSections.EventFI, oddCategory, section); err != nil {
				s.logger.Error("Error storing odd section", "eventID", event.ID, "odd", oddCategory)
				log.Printf("⚠️  Error when storing %v", err)
				errs = append(errs, err)
			}
		}
		for _, section := range parsedOddSections.OtherRes {
			if err := s.storeSection(ctx, event.ID, parsedOddSections.EventFI, "others", section); err != nil {
				s.logger.Error("Skipping result with no valid Event ID")
				errs = append(errs, err)
				continue
			}
		}

		// result := oddsData.Results[0]

	}

	for err := range errs {
		log.Printf("❌ Error: %v", err)
	}

	return nil
}

func (s *ServiceImpl) fetchBwinOdds(ctx context.Context) error {
	// getting odds for a specific event is not possible for bwin, most specific we can get is fetch odds on a single sport
	// so instead of having event and odds fetched separetly event will also be fetched along with the odds
	sportIds := []int{4, 12, 7}
	for _, sportId := range sportIds {
		url := fmt.Sprintf("https://api.b365api.com/v1/bwin/prematch?sport_id=%d&token=%s", sportId, s.config.Bet365Token)
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			log.Printf("❌ Failed to create request for sportId %d: %v", sportId, err)
			continue
		}

		resp, err := s.client.Do(req)
		if err != nil {
			log.Printf("❌ Failed to fetch request for sportId %d: %v", sportId, err)
			continue
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			log.Printf("❌ Failed to read response body for sportId %d: %v", sportId, err)
			continue
		}

		var data struct {
			Success int                      `json:"success"`
			Results []map[string]interface{} `json:"results"`
		}

		if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
			fmt.Printf("Decode failed for sport_id=%d\nRaw: %s\n", sportId, string(body))
			continue
		}

		for _, res := range data.Results {
			if getInt(res["Id"]) == -1 {
				continue
			}

			event := domain.Event{
				ID:          strconv.Itoa(getInt(res["Id"])),
				SportID:     int32(getInt(res["SportId"])),
				LeagueID:    int32(getInt(res["LeagueId"])),
				LeagueName:  getString(res["Leaguename"]),
				HomeTeam:    getString(res["HomeTeam"]),
				HomeTeamID:  int32(getInt(res["HomeTeamId"])),
				AwayTeam:    getString(res["AwayTeam"]),
				AwayTeamID:  int32(getInt(res["AwayTeamId"])),
				StartTime:   time.Now().UTC().Format(time.RFC3339),
				TimerStatus: "1",
				IsLive:      true,
				Status:      "live",
				Source:      "bwin",
			}

			if err := s.store.SaveEvent(ctx, event); err != nil {
				fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
				continue
			}

			for _, market := range []string{"Markets, optionMarkets"} {
				for _, m := range getMapArray(res[market]) {
					name := getMap(m["name"])
					marketName := getString(name["value"])

					market := domain.Market{
						EventID:        event.ID,
						MarketID:       getString(m["id"]),
						MarketCategory: getString(m["category"]),
						MarketName:     marketName,
						Source:         "bwin",
					}

					results := getMapArray(m["results"])
					market.Odds = results

					s.store.SaveNonLiveMarket(ctx, market)

				}
			}
		}

	}
	return nil
}

func (s *ServiceImpl) FetchNonLiveOddsByEventID(ctx context.Context, eventIDStr string) (domain.BaseNonLiveOddResponse, error) {

	eventID, err := strconv.ParseInt(eventIDStr, 10, 64)
	if err != nil {
		s.logger.Error("Failed to parse event id")
		return domain.BaseNonLiveOddResponse{}, err
	}

	url := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%d", s.config.Bet365Token, eventID)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		log.Printf("❌ Failed to create request for event %d: %v", eventID, err)

	}

	resp, err := s.client.Do(req)
	if err != nil {
		log.Printf("❌ Failed to fetch prematch odds for event %d: %v", eventID, err)
		return domain.BaseNonLiveOddResponse{}, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Printf("❌ Failed to read response body for event %d: %v", eventID, err)
		return domain.BaseNonLiveOddResponse{}, err
	}
	var oddsData domain.BaseNonLiveOddResponse

	if err := json.Unmarshal(body, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 {
		log.Printf("❌ Invalid prematch data for event %d", eventID)
		return domain.BaseNonLiveOddResponse{}, err
	}

	return oddsData, nil
}

func (s *ServiceImpl) ParseOddSections(ctx context.Context, res json.RawMessage, sportID int32) (domain.ParseOddSectionsRes, error) {
	var sections map[string]domain.OddsSection
	var OtherRes []domain.OddsSection
	var eventFI string
	switch sportID {
	case domain.FOOTBALL:
		var footballRes domain.FootballOddsResponse
		if err := json.Unmarshal(res, &footballRes); err != nil {
			s.logger.Error("Failed to unmarshal football result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = footballRes.FI
		sections = map[string]domain.OddsSection{
			"main":        footballRes.Main,
			"asian_lines": footballRes.AsianLines,
			"goals":       footballRes.Goals,
			"half":        footballRes.Half,
		}
	case domain.BASKETBALL:
		var basketballRes domain.BasketballOddsResponse
		if err := json.Unmarshal(res, &basketballRes); err != nil {
			s.logger.Error("Failed to unmarshal basketball result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = basketballRes.FI
		OtherRes = basketballRes.Others
		sections = map[string]domain.OddsSection{
			"main":          basketballRes.Main,
			"half_props":    basketballRes.HalfProps,
			"quarter_props": basketballRes.QuarterProps,
			"team_props":    basketballRes.TeamProps,
		}

	case domain.ICE_HOCKEY:
		var iceHockeyRes domain.IceHockeyOddsResponse
		if err := json.Unmarshal(res, &iceHockeyRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = iceHockeyRes.FI
		OtherRes = iceHockeyRes.Others
		sections = map[string]domain.OddsSection{
			"main":       iceHockeyRes.Main,
			"main_2":     iceHockeyRes.Main2,
			"1st_period": iceHockeyRes.FirstPeriod,
		}
	case domain.CRICKET:
		var cricketRes domain.CricketOddsResponse
		if err := json.Unmarshal(res, &cricketRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = cricketRes.FI
		OtherRes = cricketRes.Others
		sections = map[string]domain.OddsSection{
			"1st_over":  cricketRes.Main,
			"innings_1": cricketRes.First_Innings,
			"main":      cricketRes.Main,
			"match":     cricketRes.Match,
			"player":    cricketRes.Player,
			"team":      cricketRes.Team,
		}
	case domain.VOLLEYBALL:
		var volleyballRes domain.VolleyballOddsResponse
		if err := json.Unmarshal(res, &volleyballRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = volleyballRes.FI
		OtherRes = volleyballRes.Others
		sections = map[string]domain.OddsSection{
			"main": volleyballRes.Main,
		}
	case domain.DARTS:
		var dartsRes domain.DartsOddsResponse
		if err := json.Unmarshal(res, &dartsRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = dartsRes.FI
		OtherRes = dartsRes.Others
		sections = map[string]domain.OddsSection{
			"180s":  dartsRes.OneEightys,
			"extra": dartsRes.Extra,
			"leg":   dartsRes.Leg,
			"main":  dartsRes.Main,
		}
	case domain.FUTSAL:
		var futsalRes domain.FutsalOddsResponse
		if err := json.Unmarshal(res, &futsalRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = futsalRes.FI
		OtherRes = futsalRes.Others
		sections = map[string]domain.OddsSection{
			"main":  futsalRes.Main,
			"score": futsalRes.Score,
		}
	case domain.AMERICAN_FOOTBALL:
		var americanFootballRes domain.AmericanFootballOddsResponse
		if err := json.Unmarshal(res, &americanFootballRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = americanFootballRes.FI
		OtherRes = americanFootballRes.Others
		sections = map[string]domain.OddsSection{
			"half_props":    americanFootballRes.HalfProps,
			"main":          americanFootballRes.Main,
			"quarter_props": americanFootballRes.QuarterProps,
		}
	case domain.RUGBY_LEAGUE:
		var rugbyLeagueRes domain.RugbyLeagueOddsResponse
		if err := json.Unmarshal(res, &rugbyLeagueRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = rugbyLeagueRes.FI
		OtherRes = rugbyLeagueRes.Others
		sections = map[string]domain.OddsSection{
			"10minute": rugbyLeagueRes.TenMinute,
			"main":     rugbyLeagueRes.Main,
			"main_2":   rugbyLeagueRes.Main2,
			"player":   rugbyLeagueRes.Player,
			"Score":    rugbyLeagueRes.Score,
			"Team":     rugbyLeagueRes.Team,
		}
	case domain.RUGBY_UNION:
		var rugbyUnionRes domain.RugbyUnionOddsResponse
		if err := json.Unmarshal(res, &rugbyUnionRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = rugbyUnionRes.FI
		OtherRes = rugbyUnionRes.Others
		sections = map[string]domain.OddsSection{
			"main":   rugbyUnionRes.Main,
			"main_2": rugbyUnionRes.Main2,
			"player": rugbyUnionRes.Player,
			"Score":  rugbyUnionRes.Score,
			"Team":   rugbyUnionRes.Team,
		}
	case domain.BASEBALL:
		var baseballRes domain.BaseballOddsResponse
		if err := json.Unmarshal(res, &baseballRes); err != nil {
			s.logger.Error("Failed to unmarshal ice hockey result", "error", err)
			return domain.ParseOddSectionsRes{}, err
		}
		eventFI = baseballRes.FI
		sections = map[string]domain.OddsSection{
			"main":       baseballRes.Main,
			"mani_props": baseballRes.MainProps,
		}
	}

	return domain.ParseOddSectionsRes{
		Sections: sections,
		OtherRes: OtherRes,
		EventFI:  eventFI,
	}, nil
}

func (s *ServiceImpl) storeSection(ctx context.Context, eventID, fi, sectionName string, section domain.OddsSection) error {
	if len(section.Sp) == 0 {
		return nil
	}

	updatedAtUnix, _ := strconv.ParseInt(section.UpdatedAt, 10, 64)
	updatedAt := time.Unix(updatedAtUnix, 0)

	var errs []error
	for marketType, market := range section.Sp {
		if len(market.Odds) == 0 {
			continue
		}

		// Check if the market id is a string
		var marketIDstr string
		err := json.Unmarshal(market.ID, &marketIDstr)
		var marketIDint int64
		if err != nil {
			// check if its int
			err := json.Unmarshal(market.ID, &marketIDint)
			if err != nil {
				s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
				continue
			}
		} else {
			marketIDint, err = strconv.ParseInt(marketIDstr, 10, 64)
			if err != nil {
				s.logger.Error("Invalid market id", "marketID", marketIDstr, "marketName", market.Name)
				continue
			}
		}

		isSupported, ok := domain.SupportedMarkets[marketIDint]

		if !ok || !isSupported {
			// s.logger.Info("Unsupported market_id", "marketID", marketIDint, "marketName", market.Name)
			continue
		}

		marketOdds, err := convertRawMessage(market.Odds)
		if err != nil {
			s.logger.Error("failed to conver json.RawMessage to []map[string]interface{} for market_id: ", market.ID)
			errs = append(errs, err)
			continue
		}

		marketRecord := domain.Market{
			EventID:        eventID,
			FI:             fi,
			MarketCategory: sectionName,
			MarketType:     marketType,
			MarketName:     market.Name,
			MarketID:       marketIDstr,
			UpdatedAt:      updatedAt,
			Odds:           marketOdds,
			// bwin won't reach this code so bet365 is hardcoded for now
			Source: "bet365",
		}

		err = s.store.SaveNonLiveMarket(ctx, marketRecord)
		if err != nil {
			s.logger.Error("failed to save market", "market_id", market.ID, "error", err)
			errs = append(errs, fmt.Errorf("market %s: %w", market.ID, err))
			continue
		}
	}

	if len(errs) > 0 {
		return errors.Join(errs...)
	}
	return nil
}

func (s *ServiceImpl) GetPrematchOdds(ctx context.Context, eventID string) ([]domain.Odd, error) {
	return s.store.GetPrematchOdds(ctx, eventID)
}

func (s *ServiceImpl) GetALLPrematchOdds(ctx context.Context) ([]domain.Odd, error) {
	return s.store.GetALLPrematchOdds(ctx)
}

func (s *ServiceImpl) GetRawOddsByMarketID(ctx context.Context, marketID string, upcomingID string) (domain.RawOddsByMarketID, error) {
	rows, err := s.store.GetRawOddsByMarketID(ctx, marketID, upcomingID)
	if err != nil {
		return domain.RawOddsByMarketID{}, err
	}

	return rows, nil
}

func (s *ServiceImpl) GetPrematchOddsByUpcomingID(ctx context.Context, upcomingID string) ([]domain.Odd, error) {
	return s.store.GetPrematchOddsByUpcomingID(ctx, upcomingID)
}

func (s *ServiceImpl) GetPaginatedPrematchOddsByUpcomingID(ctx context.Context, upcomingID string, limit, offset domain.ValidInt64) ([]domain.Odd, error) {
	return s.store.GetPaginatedPrematchOddsByUpcomingID(ctx, upcomingID, limit, offset)
}

func (s *ServiceImpl) DeleteOddsForEvent(ctx context.Context, eventID string) error {
	return s.store.DeleteOddsForEvent(ctx, eventID)
}

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

func getInt(v interface{}) int {
	if n, ok := v.(float64); ok {
		return int(n)
	}
	return -1
}

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

func getMapArray(v interface{}) []map[string]interface{} {
	result := []map[string]interface{}{}
	if arr, ok := v.([]interface{}); ok {
		for _, item := range arr {
			if m, ok := item.(map[string]interface{}); ok {
				result = append(result, m)
			}
		}
	}
	return result
}

func convertRawMessage(rawMessages []json.RawMessage) ([]map[string]interface{}, error) {
	var result []map[string]interface{}
	for _, raw := range rawMessages {
		var m map[string]interface{}
		if err := json.Unmarshal(raw, &m); err != nil {
			return nil, err
		}
		result = append(result, m)
	}

	return result, nil
}
