package main

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

	"srs.epita.fr/fic-server/libfic"
)

var (
	noValidateChallenge bool
)

type Walker struct {
	LastSync     map[string]*TSValue
	Exercices    AirbusExercicesBindings
	Teams        map[string]fic.ExportedTeam
	RevTeams     map[string]string
	TeamBindings map[string]*AirbusTeam
	API          AirbusAPI
	Coeff        float64
}

func (w *Walker) fetchTeams() error {
	teams, err := w.API.GetTeams()
	if err != nil {
		return err
	}

	w.RevTeams = map[string]string{}
	w.TeamBindings = map[string]*AirbusTeam{}

	for tid, team := range w.Teams {
		for _, t := range teams {
			if team.Name == t.Name || team.ExternalId == t.Name {
				w.TeamBindings[tid] = &t
				break
			}
		}

		if _, ok := w.TeamBindings[tid]; !ok {
			log.Printf("Team binding not found: %s - %s", tid, team.Name)
		}

		w.RevTeams[team.Name] = tid
	}

	return nil
}

func (w *Walker) treat(path string) error {
	teamid := filepath.Base(filepath.Dir(path))

	if _, ok := w.TeamBindings[teamid]; ok {
		return w.TreatScoreGrid(path, w.TeamBindings[teamid])
	}

	return nil
}

func (w *Walker) LoadScoreState(path string) (int64, error) {
	mypath := filepath.Join(filepath.Dir(path), "airbus.json")
	if _, err := os.Stat(mypath); os.IsNotExist(err) {
		fd, err := os.Create(mypath)
		if err != nil {
			return 0, err
		}
		defer fd.Close()

		fd.Write([]byte("0"))
		return 0, nil
	}

	fd, err := os.Open(mypath)
	if err != nil {
		return 0, err
	}
	defer fd.Close()

	var ret int64

	jdec := json.NewDecoder(fd)
	if err := jdec.Decode(&ret); err != nil {
		return 0, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
	}

	return ret, nil
}

func (w *Walker) LoadScoreGrid(path string) ([]fic.ScoreGridRow, error) {
	fd, err := os.Open(filepath.Join(filepath.Dir(path), "scores.json"))
	if err != nil {
		return nil, err
	}
	defer fd.Close()

	var ret []fic.ScoreGridRow

	jdec := json.NewDecoder(fd)
	if err := jdec.Decode(&ret); err != nil {
		return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
	}

	return ret, nil
}

func (w *Walker) WalkScoreSync(path string, d os.DirEntry, err error) error {
	if filepath.Base(path) == "scores.json" {
		return w.treat(path)
	}

	return nil
}

func (w *Walker) loadMyFile(path string) (*fic.MyTeam, error) {
	fd, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer fd.Close()

	var ret fic.MyTeam

	jdec := json.NewDecoder(fd)
	if err := jdec.Decode(&ret); err != nil {
		return nil, fmt.Errorf("an error occurs when trying to decode airbus.json: %w", err)
	}

	return &ret, nil
}

func (w *Walker) WalkScore(path string, d os.DirEntry, err error) error {
	if filepath.Base(path) == "scores.json" {
		go w.treat(path)
	}
	return nil
}

func (w *Walker) TreatScoreGrid(path string, airbusTeam *AirbusTeam) error {
	// Read score grid
	fdscores, err := os.Open(path)
	if err != nil {
		return err
	}
	defer fdscores.Close()

	teamscores, err := fic.ReadScoreGrid(fdscores)
	if err != nil {
		return err
	}

	// Found all new entries
	maxts := &TSValue{
		Time: time.Time{},
	}
	if ts, ok := w.LastSync[airbusTeam.Name]; ok {
		maxts = ts
	}
	for _, row := range teamscores {
		if row.Time.After(maxts.Time) {
			maxts.Time = row.Time
		}
		if ts, ok := w.LastSync[airbusTeam.Name]; !ok || row.Time.After(ts.Time) {
			if !noValidateChallenge && row.Reason == "Validation" {
				err = w.API.ValidateChallengeFromUser(airbusTeam, w.Exercices[row.IdExercice])
			} else {
				err = w.API.AwardUser(airbusTeam, int64(row.Points*row.Coeff*w.Coeff), row.Reason)
			}

			if err != nil {
				return err
			}

			maxts.Score += int64(row.Points * row.Coeff * w.Coeff)
		}
	}

	w.LastSync[airbusTeam.Name] = maxts

	return nil
}

func (w *Walker) BalanceScores() error {
	for team, ts := range w.LastSync {
		team_id, ok := w.RevTeams[team]
		if !ok {
			continue
		}

		myteam, err := w.loadMyFile(filepath.Join(TeamsDir, team_id, "my.json"))
		if err != nil {
			return fmt.Errorf("Unable to open %s/my.json: %w", team_id, err)
		}

		airbusTeam := w.TeamBindings[fmt.Sprintf("%d", myteam.Id)]

		if ts.Score != myteam.Points*int64(w.Coeff) {
			err := w.API.AwardUser(airbusTeam, myteam.Points*int64(w.Coeff)-ts.Score, "Équilibrage")
			if err != nil {
				return fmt.Errorf("Unable to open %s/my.json: %w", team, err)
			}

			w.LastSync[airbusTeam.Name].Score = myteam.Points * int64(w.Coeff)
			w.LastSync[airbusTeam.Name].Time = time.Now()
		}
	}

	return nil
}
