package daemon

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sort"
	"sync"
	"time"

	"gordenko.dev/dima/flashscore"
	"gordenko.dev/dima/flashscore/model"
	"gordenko.dev/dima/qx"
	"gordenko.dev/dima/web"
)

const (
	defaultCheckInterval = 1 * time.Hour
)

type Options struct {
	BaseDir       string
	CheckInterval int
	Logger        *log.Logger
	Db            *qx.Db
	SportIDs      []int
}

type Daemon struct {
	mutex         sync.Mutex
	checkInterval time.Duration
	fs            *flashscore.Flashscore
	baseDir       string
	dumpFilepath  string
	logger        *log.Logger
	port          int
	model         *model.Model
	// map[sportID]map[matchID]
	reports map[int]map[string]flashscore.UpcomingMatchReport
}

func New(opt Options) (*Daemon, error) {
	if opt.Logger == nil {
		return nil, errors.New("Logger option is required")
	}
	if opt.Db == nil {
		return nil, errors.New("Db option is required")
	}
	if opt.BaseDir == "" {
		return nil, errors.New("BaseDir option is required")
	}
	s := &Daemon{
		logger:       opt.Logger,
		baseDir:      opt.BaseDir,
		dumpFilepath: filepath.Join(opt.BaseDir, "reports.dump"),
		fs:           flashscore.New(opt.Db, opt.Logger),
		model:        model.New(opt.Db),
		reports:      make(map[int]map[string]flashscore.UpcomingMatchReport),
	}
	if opt.CheckInterval <= 0 {
		s.checkInterval = defaultCheckInterval
	} else {
		s.checkInterval = time.Duration(opt.CheckInterval) * time.Second
	}
	for _, sportID := range opt.SportIDs {
		s.reports[sportID] = make(map[string]flashscore.UpcomingMatchReport)
	}
	err := s.tryRestoreReportsFromDump()
	if err != nil {
		// только логгируем, ибо если файла нет или он поврежден - это не проблема
		s.logger.Printf("tryRestoreReportsFromDump: %s\n", err)
	}
	return s, nil
}

func (s *Daemon) Close() {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	buf, err := json.MarshalIndent(s.reports, "", "    ")
	if err != nil {
		return
	}

	err = os.WriteFile(s.dumpFilepath, buf, 0666)
	if err != nil {
		return
	}
}

func (s *Daemon) tryRestoreReportsFromDump() (err error) {
	buf, err := os.ReadFile(s.dumpFilepath)
	if err != nil {
		return
	}
	return json.Unmarshal(buf, &s.reports)
}

func (s *Daemon) ShowReports(state *web.State, reply *web.Reply) (err error) {
	buf, _ := json.MarshalIndent(s.reports, "", "    ")
	str := fmt.Sprintf("%s", buf)

	reply.WriteString(str)
	return nil
}

func (s *Daemon) Run() {
	ticker := time.NewTicker(s.checkInterval)

	s.logger.Println("tick")
	for sportID := range s.reports {
		s.checkUpcomingMatches(sportID)
	}
	s.logger.Println("tick processed")

	for {
		select {
		case <-ticker.C:
			s.logger.Println("tick")
			for sportID := range s.reports {
				s.checkUpcomingMatches(sportID)
			}
			s.logger.Println("tick processed")
		}
	}
}

func (s *Daemon) ListUpcomingMatchReports(sportID int) (list []flashscore.UpcomingMatchReport, err error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	now := time.Now().Unix() - 3600

	reports, ok := s.reports[sportID]
	if !ok {
		return
	}

	fmt.Printf("sportID: %d, reports size: %d\n", sportID, len(reports))

	var started []string

	for matchID, report := range reports {
		if report.StartTime < now {
			// Матч уже начался
			//fmt.Printf("matchStarted: startTime %d < now %d\n", report.StartTime, now)
			started = append(started, matchID)
		} else {
			if report.IsTeamsLoaded && report.IsHistoryLoaded {
				list = append(list, report)
			}
		}
	}

	// Удаляем матчи, которые начались
	for _, matchID := range started {
		delete(reports, matchID)
	}

	// Сортируем матчи
	sort.Slice(list, func(i, j int) bool {
		a := list[i]
		b := list[j]

		if a.StartTime == b.StartTime {
			// Время начала равно - сравниваем MatchID
			return a.MatchID < b.MatchID
		}

		return a.StartTime < b.StartTime
	})

	return
}

func (s *Daemon) checkUpcomingMatches(sportID int) {
	scheduledMatches, err := s.fs.ListOfScheduledMatches(sportID)
	if err != nil {
		s.logger.Printf("ListOfScheduledMatches: %s\n", err)
		return
	}

	s.logger.Printf("Sport: %d, scheduled matches found: %d\n", sportID, len(scheduledMatches))

	reports := s.reports[sportID]

	for idx, match := range scheduledMatches {
		if sportID == flashscore.Tennis {
			// Пары игнорим
			if flashscore.IsTennisDoublesChamp(match.FullChampName) {
				continue
			}
		}
		// Здесь лок не нужен, ибо нет race conditions, пишем в мапу в этом же потоке
		report, ok := reports[match.MatchID]
		if !ok {
			report = flashscore.UpcomingMatchReport{
				MatchID:   match.MatchID,
				SportID:   match.SportID,
				StartTime: match.StartTime.Unix(),
				Zone: model.TeamZone{
					ZoneID:  match.Zone.ZoneID,
					Name:    match.Zone.Name,
					SportID: match.SportID,
				},
				Champ: model.TeamChamp{
					ChampID: match.ChampID,
				},
				FullChampName: match.FullChampName,
			}
			// Ниже по тексту report будет перезаписан в коллекции reports
		}

		// Даже если отчет полный - синкаем, чтобы обновить odds

		err = s.fs.SyncUpcomingMatchReport(&report)
		if err != nil {
			s.logger.Printf("SyncUpcomingMatchReport: %s; matchID=%s\n", err, report.MatchID)
		} else {
			report.ParseTime = time.Now()
			//buf, _ := json.MarshalIndent(report, "", "    ")
			//fmt.Printf("REPORT:\n\n%s\n\n", buf)

			s.mutex.Lock()
			reports[report.MatchID] = report
			s.mutex.Unlock()
		}

		//fmt.Printf("REPORT:\n\n%s\n\n", buf)
		if (idx % 10) == 0 {
			s.logger.Printf("%d matches scanned\n", idx+1)
		}
	}

	s.logger.Printf("Sport: %d, all matches scanned\n", sportID)
}

/*
func (s *Flashscore) ListUpcomingMatchReports(sportID int, reportCh chan *UpcomingMatchReport) (err error) {
	matches, err := s.listOfScheduledMatches(sportID)
	if err != nil {
		err = fmt.Errorf("listOfScheduledMatches: %s", err)
		return
	}

	//matches = DiscardScheduledMatchesFurtherThan(matches, 24*time.Hour)
	reports := s.reports

	for _, match := range matches {
		s.Li

		report = &UpcomingMatchReport{
			IsComplete: true,
			MatchID:    match.MatchID,
			SportID:    match.SportID,
			StartTime:  match.StartTime.Unix(),
		}

		report, reportErr := s.getUpcomingMatchReport(match)
		if reportErr != nil {
			s.logger.Printf("getUpcomingMatchReport: %s; matchID=%s", reportErr, match.MatchID)
		} else {
			reportCh <- &report
		}
	}

	close(reportCh)
	return
}
*/
