package event

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

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

type service struct {
	token string
	store *repository.Store
}

func New(token string, store *repository.Store) Service {
	return &service{
		token: token,
		store: store,
	}
}

func (s *service) FetchLiveEvents(ctx context.Context) error {
	var wg sync.WaitGroup
	urls := []struct {
		name   string
		source string
	}{
		{"https://api.b365api.com/v1/bet365/inplay?sport_id=%d&token=%s", "bet365"},
		{"https://api.b365api.com/v1/betfair/sb/inplay?sport_id=%d&token=%s", "betfair"},
		{"https://api.b365api.com/v1/1xbet/inplay?sport_id=%d&token=%s", "1xbet"},
	}

	for _, url := range urls {
		wg.Add(1)

		go func() {
			defer wg.Done()
			s.fetchLiveEvents(ctx, url.name, url.source)
		}()
	}
	wg.Wait()
	return nil
}

func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error {
	sportIDs := []int{1, 13, 78, 18, 91, 16, 17, 14, 12, 3, 2, 4, 83, 15, 92, 94, 8, 19, 36, 66, 9, 75, 90, 95, 110, 107, 151, 162, 148}

	var wg sync.WaitGroup

	for _, sportID := range sportIDs {
		wg.Add(1)
		go func(sportID int) {
			defer wg.Done()

			url := fmt.Sprintf(url, sportID, s.token)
			resp, err := http.Get(url)
			if err != nil {
				fmt.Printf(" Failed request for sport_id=%d: %v\n", sportID, err)
				return
			}
			defer resp.Body.Close()

			body, _ := io.ReadAll(resp.Body)

			events := []domain.Event{}
			switch source {
			case "bet365":
				events = handleBet365prematch(body, sportID, source)
			case "betfair":
				events = handleBetfairprematch(body, sportID, source)
			case "1xbet":
				// betfair and 1xbet have the same result structure
				events = handleBetfairprematch(body, sportID, source)
			}

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

	wg.Wait()
	fmt.Println("All live events fetched and stored.")
	return nil

}

func handleBet365prematch(body []byte, sportID int, source string) []domain.Event {
	var data struct {
		Success int                        `json:"success"`
		Results [][]map[string]interface{} `json:"results"`
	}

	events := []domain.Event{}
	if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
		fmt.Printf("%s: Decode failed for sport_id=%d\nRaw: %s\n", source, sportID, string(body))
		return events
	}

	for _, group := range data.Results {
		for _, ev := range group {
			if getString(ev["type"]) != "EV" {
				continue
			}

			event := domain.Event{
				ID:           getString(ev["ID"]),
				SportID:      int32(sportID),
				MatchName:    getString(ev["NA"]),
				Score:        getString(ev["SS"]),
				MatchMinute:  getInt(ev["TM"]),
				TimerStatus:  getString(ev["TT"]),
				HomeTeamID:   getInt32(ev["HT"]),
				AwayTeamID:   getInt32(ev["AT"]),
				HomeKitImage: getString(ev["K1"]),
				AwayKitImage: getString(ev["K2"]),
				LeagueName:   getString(ev["CT"]),
				LeagueID:     getInt32(ev["C2"]),
				LeagueCC:     getString(ev["CB"]),
				StartTime:    time.Now().UTC().Format(time.RFC3339),
				IsLive:       true,
				Status:       "live",
				MatchPeriod:  getInt(ev["MD"]),
				AddedTime:    getInt(ev["TA"]),
				Source:       source,
			}

			events = append(events, event)
		}
	}

	return events
}

func handleBetfairprematch(body []byte, sportID int, source string) []domain.Event {
	var data struct {
		Success int                      `json:"success"`
		Results []map[string]interface{} `json:"results"`
	}

	events := []domain.Event{}
	if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
		fmt.Printf("%s: Decode failed for sport_id=%d\nRaw: %s\n", source, sportID, string(body))
		return events
	}

	for _, ev := range data.Results {
		homeRaw, _ := ev["home"].(map[string]interface{})
		awayRaw, _ := ev["home"].(map[string]interface{})

		event := domain.Event{
			ID:          getString(ev["id"]),
			SportID:     int32(sportID),
			TimerStatus: getString(ev["time_status"]),
			HomeTeamID:  getInt32(homeRaw["id"]),
			AwayTeamID:  getInt32(awayRaw["id"]),
			StartTime:   time.Now().UTC().Format(time.RFC3339),
			IsLive:      true,
			Status:      "live",
			Source:      source,
		}

		events = append(events, event)
	}

	return events
}

func (s *service) FetchUpcomingEvents(ctx context.Context) error {
	var wg sync.WaitGroup
	urls := []struct {
		name   string
		source string
	}{
		{"https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", "bet365"},
		// {"https://api.b365api.com/v1/betfair/sb/upcoming?sport_id=%d&token=%s&page=%d", "betfair"},
		// {"https://api.b365api.com/v1/1xbet/upcoming?sport_id=%d&token=%s&page=%d", "1xbet"},
	}

	for _, url := range urls {
		wg.Add(1)

		go func() {
			defer wg.Done()
			s.fetchUpcomingEventsFromProvider(ctx, url.name, url.source)
		}()
	}

	wg.Wait()
	return nil
}

func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, source string) {
	sportIDs := []int{1, 18, 17, 3, 83, 15, 12, 19, 8, 16, 91}
	// TODO: Add the league skipping again when we have dynamic leagues
	// b, err := os.OpenFile("logs/skipped_leagues.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	// if err != nil {
	// 	log.Printf("❌ Failed to open leagues file %v", err)
	// 	return
	// }
	for sportIndex, sportID := range sportIDs {
		var totalPages int = 1
		var page int = 0
		var limit int = 200
		var count int = 0
		log.Printf("Sport ID %d", sportID)
		for page <= totalPages {
			page = page + 1
			url := fmt.Sprintf(url, sportID, s.token, page)
			log.Printf("📡 Fetching data from %s - sport %d (%d/%d), for event data page (%d/%d)",
				source, sportID, sportIndex+1, len(sportIDs), page, totalPages)
			resp, err := http.Get(url)
			if err != nil {
				log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
				continue
			}
			defer resp.Body.Close()

			body, _ := io.ReadAll(resp.Body)
			var data domain.BetResult

			if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
				log.Printf("❌ Failed to parse json data")
				continue
			}
			var skippedLeague []string
			for _, ev := range data.Results {
				startUnix, _ := strconv.ParseInt(ev.Time, 10, 64)
				// eventID, err := strconv.ParseInt(ev.ID, 10, 64)
				// if err != nil {
				// 	log.Panicf("❌ Invalid event id,  eventID %v", ev.ID)
				// 	continue
				// }

				leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64)
				if err != nil {
					log.Printf("❌ Invalid league id,  leagueID %v", ev.League.ID)
					continue
				}

				// doesn't make sense to save and check back to back, but for now it can be here
				// no this its fine to keep it here
				// but change the league id to bet365 id later
				err = s.store.SaveLeague(ctx, domain.League{
					ID:       leagueID,
					Name:     ev.League.Name,
					IsActive: true,
					SportID:  convertInt32(ev.SportID),
				})

				if err != nil {
					log.Printf("❌ Error Saving League on  %v", ev.League.Name)
					log.Printf("err:%v", err)
					continue
				}

				if supported, err := s.store.CheckLeagueSupport(ctx, leagueID); !supported || err != nil {
					log.Printf("Skipping league %v", ev.League.Name)
					skippedLeague = append(skippedLeague, ev.League.Name)
					continue
				}

				event := domain.UpcomingEvent{
					ID:           ev.ID,
					SportID:      convertInt32(ev.SportID),
					MatchName:    "",
					HomeTeam:     ev.Home.Name,
					AwayTeam:     "", // handle nil safely
					HomeTeamID:   convertInt32(ev.Home.ID),
					AwayTeamID:   0,
					HomeKitImage: "",
					AwayKitImage: "",
					LeagueID:     convertInt32(ev.League.ID),
					LeagueName:   ev.League.Name,
					LeagueCC:     "",
					StartTime:    time.Unix(startUnix, 0).UTC(),
					Source:       source,
				}

				if ev.Away != nil {
					event.AwayTeam = ev.Away.Name
					event.AwayTeamID = convertInt32(ev.Away.ID)
					event.MatchName = ev.Home.Name + " vs " + ev.Away.Name
				}

				err = s.store.SaveUpcomingEvent(ctx, event)
				if err != nil {
					log.Printf("❌ Failed to save upcoming event %s", event.ID)
				}
			}

			log.Printf("⚠️  Skipped leagues %v", len(skippedLeague))
			log.Printf("⚠️  Total pages %v", data.Pager.Total/data.Pager.PerPage)
			totalPages = data.Pager.Total / data.Pager.PerPage

			if count >= limit {
				break
			}
			if page > totalPages {
				break
			}
			count++
		}
	}
}

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

func getInt(v interface{}) int {
	if f, ok := v.(float64); ok {
		return int(f)
	}
	return 0
}

func getInt32(v interface{}) int32 {
	if n, err := strconv.Atoi(getString(v)); err == nil {
		return int32(n)
	}
	return 0
}

func convertInt32(num string) int32 {
	if n, err := strconv.Atoi(num); err == nil {
		return int32(n)
	}
	return 0
}
func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) {
	return s.store.GetAllUpcomingEvents(ctx)
}

func (s *service) GetExpiredUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, error) {
	return s.store.GetExpiredUpcomingEvents(ctx, filter)
}

func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, filter domain.EventFilter) ([]domain.UpcomingEvent, int64, error) {
	return s.store.GetPaginatedUpcomingEvents(ctx, filter)
}

func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) {
	return s.store.GetUpcomingEventByID(ctx, ID)
}

func (s *service) UpdateFinalScore(ctx context.Context, eventID, fullScore string, status domain.EventStatus) error {
	return s.store.UpdateFinalScore(ctx, eventID, fullScore, status)
}
func (s *service) UpdateEventStatus(ctx context.Context, eventID string, status domain.EventStatus) error {
	return s.store.UpdateEventStatus(ctx, eventID, status)
}

// func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error {
//     url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.token, eventID)

//     resp, err := http.Get(url)
//     if err != nil {
//         return fmt.Errorf("failed to fetch result: %w", err)
//     }
//     defer resp.Body.Close()

//     body, _ := io.ReadAll(resp.Body)

//     // Parse the API response
//     var apiResp struct {
//         Results []struct {
//             ID     string `json:"id"`
//             Ss     string `json:"ss"` // Full-time score
//             Status string `json:"time_status"`
//         } `json:"results"`
//     }

//     err = json.Unmarshal(body, &apiResp)
//     if err != nil || len(apiResp.Results) == 0 {
//         return fmt.Errorf("invalid response or no results found")
//     }

//     result := apiResp.Results[0]

//     err = s.store.UpdateFinalScore(ctx, result.ID, result.Ss, result.Status)
//     if err != nil {
//         return fmt.Errorf("failed to update final score in database: %w", err)
//     }

//     return nil
// }
