package database

import (
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"time"

	_ "modernc.org/sqlite"
)

type DB struct {
	conn *sql.DB
}

func Open() (*DB, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return nil, err
	}

	dbDir := filepath.Join(home, ".termdoku")
	if err := os.MkdirAll(dbDir, 0o755); err != nil {
		return nil, err
	}

	dbPath := filepath.Join(dbDir, "termdoku.db")
	conn, err := sql.Open("sqlite", dbPath)
	if err != nil {
		return nil, err
	}

	db := &DB{conn: conn}
	if err := db.initialize(); err != nil {
		conn.Close()
		return nil, err
	}

	return db, nil
}

func (db *DB) Close() error {
	if db.conn != nil {
		return db.conn.Close()
	}
	return nil
}

func (db *DB) initialize() error {
	schema := `
	CREATE TABLE IF NOT EXISTS games (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		difficulty TEXT NOT NULL,
		completed BOOLEAN NOT NULL,
		time_seconds INTEGER NOT NULL,
		hints_used INTEGER NOT NULL,
		date DATETIME NOT NULL,
		is_daily BOOLEAN NOT NULL,
		daily_seed TEXT,
		created_at DATETIME DEFAULT CURRENT_TIMESTAMP
	);

	CREATE TABLE IF NOT EXISTS achievements (
		id TEXT PRIMARY KEY,
		name TEXT NOT NULL,
		description TEXT NOT NULL,
		icon TEXT NOT NULL,
		unlocked BOOLEAN NOT NULL DEFAULT 0,
		progress INTEGER NOT NULL DEFAULT 0,
		target INTEGER NOT NULL,
		unlocked_at DATETIME,
		updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
	);

	CREATE TABLE IF NOT EXISTS stats (
		id INTEGER PRIMARY KEY CHECK (id = 1),
		total_games INTEGER NOT NULL DEFAULT 0,
		completed_games INTEGER NOT NULL DEFAULT 0,
		current_streak INTEGER NOT NULL DEFAULT 0,
		best_streak INTEGER NOT NULL DEFAULT 0,
		last_played_date TEXT,
		hints_used INTEGER NOT NULL DEFAULT 0,
		updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
	);

	CREATE TABLE IF NOT EXISTS best_times (
		difficulty TEXT PRIMARY KEY,
		time_seconds INTEGER NOT NULL,
		hints_used INTEGER NOT NULL,
		achieved_at DATETIME NOT NULL
	);

	CREATE INDEX IF NOT EXISTS idx_games_difficulty ON games(difficulty);
	CREATE INDEX IF NOT EXISTS idx_games_date ON games(date);
	CREATE INDEX IF NOT EXISTS idx_games_daily_seed ON games(daily_seed);
	`

	_, err := db.conn.Exec(schema)
	if err != nil {
		return fmt.Errorf("failed to initialize schema: %w", err)
	}

	var count int
	err = db.conn.QueryRow("SELECT COUNT(*) FROM stats").Scan(&count)
	if err != nil {
		return err
	}
	if count == 0 {
		_, err = db.conn.Exec("INSERT INTO stats (id) VALUES (1)")
		if err != nil {
			return err
		}
	}

	return nil
}

type GameRecord struct {
	ID          int
	Difficulty  string
	Completed   bool
	TimeSeconds int
	HintsUsed   int
	Date        time.Time
	IsDaily     bool
	DailySeed   string
}

func (db *DB) SaveGame(record GameRecord) error {
	query := `
		INSERT INTO games (difficulty, completed, time_seconds, hints_used, date, is_daily, daily_seed)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`
	_, err := db.conn.Exec(query,
		record.Difficulty,
		record.Completed,
		record.TimeSeconds,
		record.HintsUsed,
		record.Date,
		record.IsDaily,
		record.DailySeed,
	)
	return err
}

func (db *DB) GetLeaderboard(difficulty string, limit int) ([]GameRecord, error) {
	query := `
		SELECT id, difficulty, completed, time_seconds, hints_used, date, is_daily, daily_seed
		FROM games
		WHERE difficulty = ? AND completed = 1
		ORDER BY time_seconds ASC, hints_used ASC
		LIMIT ?
	`
	rows, err := db.conn.Query(query, difficulty, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var records []GameRecord
	for rows.Next() {
		var r GameRecord
		var dailySeed sql.NullString
		err := rows.Scan(&r.ID, &r.Difficulty, &r.Completed, &r.TimeSeconds, &r.HintsUsed, &r.Date, &r.IsDaily, &dailySeed)
		if err != nil {
			return nil, err
		}
		if dailySeed.Valid {
			r.DailySeed = dailySeed.String
		}
		records = append(records, r)
	}
	return records, rows.Err()
}

type Stats struct {
	TotalGames     int
	CompletedGames int
	CurrentStreak  int
	BestStreak     int
	LastPlayedDate string
	HintsUsed      int
}

func (db *DB) GetStats() (*Stats, error) {
	query := "SELECT total_games, completed_games, current_streak, best_streak, last_played_date, hints_used FROM stats WHERE id = 1"
	var stats Stats
	var lastPlayed sql.NullString
	err := db.conn.QueryRow(query).Scan(
		&stats.TotalGames,
		&stats.CompletedGames,
		&stats.CurrentStreak,
		&stats.BestStreak,
		&lastPlayed,
		&stats.HintsUsed,
	)
	if err != nil {
		return nil, err
	}
	if lastPlayed.Valid {
		stats.LastPlayedDate = lastPlayed.String
	}
	return &stats, nil
}

func (db *DB) UpdateStats(stats *Stats) error {
	query := `
		UPDATE stats 
		SET total_games = ?, completed_games = ?, current_streak = ?, 
		    best_streak = ?, last_played_date = ?, hints_used = ?, updated_at = CURRENT_TIMESTAMP
		WHERE id = 1
	`
	_, err := db.conn.Exec(query,
		stats.TotalGames,
		stats.CompletedGames,
		stats.CurrentStreak,
		stats.BestStreak,
		stats.LastPlayedDate,
		stats.HintsUsed,
	)
	return err
}

type Achievement struct {
	ID          string
	Name        string
	Description string
	Icon        string
	Unlocked    bool
	Progress    int
	Target      int
	UnlockedAt  *time.Time
}

func (db *DB) GetAchievements() (map[string]*Achievement, error) {
	query := "SELECT id, name, description, icon, unlocked, progress, target, unlocked_at FROM achievements"
	rows, err := db.conn.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	achievements := make(map[string]*Achievement)
	for rows.Next() {
		var a Achievement
		var unlockedAt sql.NullTime
		err := rows.Scan(&a.ID, &a.Name, &a.Description, &a.Icon, &a.Unlocked, &a.Progress, &a.Target, &unlockedAt)
		if err != nil {
			return nil, err
		}
		if unlockedAt.Valid {
			a.UnlockedAt = &unlockedAt.Time
		}
		achievements[a.ID] = &a
	}
	return achievements, rows.Err()
}

func (db *DB) SaveAchievement(ach *Achievement) error {
	query := `
		INSERT OR REPLACE INTO achievements (id, name, description, icon, unlocked, progress, target, unlocked_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
	`
	_, err := db.conn.Exec(query,
		ach.ID,
		ach.Name,
		ach.Description,
		ach.Icon,
		ach.Unlocked,
		ach.Progress,
		ach.Target,
		ach.UnlockedAt,
	)
	return err
}

func (db *DB) GetBestTime(difficulty string) (int, bool, error) {
	query := "SELECT time_seconds FROM best_times WHERE difficulty = ?"
	var timeSeconds int
	err := db.conn.QueryRow(query, difficulty).Scan(&timeSeconds)
	if err == sql.ErrNoRows {
		return 0, false, nil
	}
	if err != nil {
		return 0, false, err
	}
	return timeSeconds, true, nil
}

func (db *DB) UpdateBestTime(difficulty string, timeSeconds int, hintsUsed int) error {
	query := `
		INSERT OR REPLACE INTO best_times (difficulty, time_seconds, hints_used, achieved_at)
		VALUES (?, ?, ?, CURRENT_TIMESTAMP)
	`
	_, err := db.conn.Exec(query, difficulty, timeSeconds, hintsUsed)
	return err
}

func (db *DB) GetRecentGames(limit int) ([]GameRecord, error) {
	query := `
		SELECT id, difficulty, completed, time_seconds, hints_used, date, is_daily, daily_seed
		FROM games
		ORDER BY date DESC
		LIMIT ?
	`
	rows, err := db.conn.Query(query, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var records []GameRecord
	for rows.Next() {
		var r GameRecord
		var dailySeed sql.NullString
		err := rows.Scan(&r.ID, &r.Difficulty, &r.Completed, &r.TimeSeconds, &r.HintsUsed, &r.Date, &r.IsDaily, &dailySeed)
		if err != nil {
			return nil, err
		}
		if dailySeed.Valid {
			r.DailySeed = dailySeed.String
		}
		records = append(records, r)
	}
	return records, rows.Err()
}
