package stats

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"time"
)

// Stats tracks player statistics and achievements
type Stats struct {
	TotalGames       int                   `json:"totalGames"`
	CompletedGames   int                   `json:"completedGames"`
	CurrentStreak    int                   `json:"currentStreak"`
	BestStreak       int                   `json:"bestStreak"`
	LastPlayedDate   string                `json:"lastPlayedDate"` // YYYY-MM-DD format
	BestTimes        map[string]int        `json:"bestTimes"`      // difficulty -> seconds
	CompletionCounts map[string]int        `json:"completionCounts"`
	HintsUsed        int                   `json:"hintsUsed"`
	RecentGames      []GameRecord          `json:"recentGames"`
	DailyHistory     map[string]GameRecord `json:"dailyHistory"` // date -> game record
}

// GameRecord represents a completed game
type GameRecord struct {
	Difficulty string    `json:"difficulty"`
	Completed  bool      `json:"completed"`
	Time       int       `json:"time"` // seconds
	HintsUsed  int       `json:"hintsUsed"`
	Date       time.Time `json:"date"`
	IsDaily    bool      `json:"isDaily"`
	DailySeed  string    `json:"dailySeed,omitempty"`
}

// Default returns a new Stats with zero values
func Default() Stats {
	return Stats{
		BestTimes:        make(map[string]int),
		CompletionCounts: make(map[string]int),
		RecentGames:      []GameRecord{},
		DailyHistory:     make(map[string]GameRecord),
	}
}

// path returns the stats file path
func path() (string, error) {
	h, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}
	return filepath.Join(h, ".termdoku", "stats.json"), nil
}

// Load reads stats from disk
func Load() (Stats, error) {
	st := Default()
	p, err := path()
	if err != nil {
		return st, err
	}
	b, err := os.ReadFile(p)
	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			return st, nil
		}
		return st, err
	}
	if err := json.Unmarshal(b, &st); err != nil {
		return st, err
	}
	return st, nil
}

// Save writes stats to disk
func Save(st Stats) error {
	p, err := path()
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
		return err
	}
	data, err := json.MarshalIndent(st, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(p, data, 0o644)
}

// RecordGame records a completed or abandoned game
func (s *Stats) RecordGame(record GameRecord) {
	s.TotalGames++

	if record.Completed {
		s.CompletedGames++

		today := time.Now().Format("2006-01-02")
		if s.LastPlayedDate == "" {
			s.CurrentStreak = 1
		} else {
			lastDate, _ := time.Parse("2006-01-02", s.LastPlayedDate)
			daysDiff := int(time.Since(lastDate).Hours() / 24)
			switch daysDiff {
			case 0:
				// Same day, maintain streak
			case 1:
				// Consecutive day
				s.CurrentStreak++
			default:
				// Streak broken
				s.CurrentStreak = 1
			}
		}
		s.LastPlayedDate = today

		if s.CurrentStreak > s.BestStreak {
			s.BestStreak = s.CurrentStreak
		}

		if record.Time > 0 {
			if existing, ok := s.BestTimes[record.Difficulty]; !ok || record.Time < existing {
				s.BestTimes[record.Difficulty] = record.Time
			}
		}
		s.CompletionCounts[record.Difficulty]++

		if record.IsDaily && record.DailySeed != "" {
			s.DailyHistory[record.DailySeed] = record
		}
	}

	// Track hints
	s.HintsUsed += record.HintsUsed

	// Add to recent games (keep last 50)
	s.RecentGames = append([]GameRecord{record}, s.RecentGames...)
	if len(s.RecentGames) > 50 {
		s.RecentGames = s.RecentGames[:50]
	}
}

// GetLeaderboard returns top N game records by time for a difficulty
func (s *Stats) GetLeaderboard(difficulty string, limit int) []GameRecord {
	var records []GameRecord
	for _, game := range s.RecentGames {
		if game.Difficulty == difficulty && game.Completed && game.Time > 0 {
			records = append(records, game)
		}
	}

	// Sort by time (ascending)
	sort.Slice(records, func(i, j int) bool {
		return records[i].Time < records[j].Time
	})

	if len(records) > limit {
		records = records[:limit]
	}

	return records
}

// HasPlayedDaily checks if the user has played today's daily
func (s *Stats) HasPlayedDaily(seed string) bool {
	_, ok := s.DailyHistory[seed]
	return ok
}

// GetDailyRecord returns the record for a specific daily seed
func (s *Stats) GetDailyRecord(seed string) (GameRecord, bool) {
	record, ok := s.DailyHistory[seed]
	return record, ok
}

// FormatTime formats seconds into MM:SS
func FormatTime(seconds int) string {
	mins := seconds / 60
	secs := seconds % 60
	return fmt.Sprintf("%02d:%02d", mins, secs)
}
