package db

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

	_ "modernc.org/sqlite"
)

type DB struct {
	conn *sql.DB
}

func New() (*DB, error) {
	dbDir := os.Getenv("CALTRACK_DATA_DIR")
	if dbDir == "" {
		home, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}
		dbDir = filepath.Join(home, ".config", "caltrack")
	}
	if err := os.MkdirAll(dbDir, 0755); err != nil {
		return nil, err
	}

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

	conn.SetMaxOpenConns(1)

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

	return db, nil
}

func (db *DB) Close() error {
	return db.conn.Close()
}

func (db *DB) migrate() error {
	_, err := db.conn.Exec(`
		CREATE TABLE IF NOT EXISTS entries (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			date TEXT NOT NULL,
			time TEXT NOT NULL,
			type TEXT NOT NULL CHECK(type IN ('food', 'exercise')),
			description TEXT NOT NULL,
			kcal INTEGER NOT NULL,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP
		);

		CREATE TABLE IF NOT EXISTS daily_summary (
			date TEXT PRIMARY KEY,
			total_consumed INTEGER NOT NULL DEFAULT 0,
			total_burned INTEGER NOT NULL DEFAULT 0,
			target_kcal INTEGER NOT NULL DEFAULT 0,
			net_kcal INTEGER NOT NULL DEFAULT 0
		);

		CREATE TABLE IF NOT EXISTS weigh_ins (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			date TEXT NOT NULL,
			weight_kg REAL NOT NULL,
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP
		);

		CREATE TABLE IF NOT EXISTS products (
			id INTEGER PRIMARY KEY AUTOINCREMENT,
			name TEXT NOT NULL,
			brand TEXT NOT NULL DEFAULT '',
			serving_size REAL NOT NULL DEFAULT 0,
			serving_unit TEXT NOT NULL DEFAULT '',
			kcal_per_serving INTEGER NOT NULL,
			source TEXT NOT NULL DEFAULT 'ai',
			created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
			last_used DATETIME DEFAULT CURRENT_TIMESTAMP
		);

		CREATE INDEX IF NOT EXISTS idx_entries_date ON entries(date);
		CREATE INDEX IF NOT EXISTS idx_weigh_ins_date ON weigh_ins(date);
		CREATE INDEX IF NOT EXISTS idx_products_name ON products(name);
	`)
	return err
}

func (db *DB) AddEntry(entry types.Entry) (types.Entry, error) {
	now := time.Now()
	if entry.Date == "" {
		entry.Date = now.Format("2006-01-02")
	}
	if entry.Time == "" {
		entry.Time = now.Format("15:04")
	}

	res, err := db.conn.Exec(
		`INSERT INTO entries (date, time, type, description, kcal) VALUES (?, ?, ?, ?, ?)`,
		entry.Date, entry.Time, entry.Type, entry.Description, entry.Kcal,
	)
	if err != nil {
		return entry, err
	}

	entry.ID, _ = res.LastInsertId()
	entry.CreatedAt = now

	if err := db.updateDailySummary(entry.Date); err != nil {
		return entry, err
	}

	return entry, nil
}

func (db *DB) ClearDay(date string) error {
	_, err := db.conn.Exec(`DELETE FROM entries WHERE date = ?`, date)
	if err != nil {
		return err
	}

	_, err = db.conn.Exec(`DELETE FROM daily_summary WHERE date = ?`, date)
	return err
}

func (db *DB) DeleteEntry(id int64) error {
	var date string
	err := db.conn.QueryRow(`SELECT date FROM entries WHERE id = ?`, id).Scan(&date)
	if err != nil {
		return err
	}

	_, err = db.conn.Exec(`DELETE FROM entries WHERE id = ?`, id)
	if err != nil {
		return err
	}

	return db.updateDailySummary(date)
}

func (db *DB) GetEntriesByDate(date string) ([]types.Entry, error) {
	rows, err := db.conn.Query(
		`SELECT id, date, time, type, description, kcal, created_at FROM entries WHERE date = ? ORDER BY time ASC`,
		date,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var entries []types.Entry
	for rows.Next() {
		var e types.Entry
		if err := rows.Scan(&e.ID, &e.Date, &e.Time, &e.Type, &e.Description, &e.Kcal, &e.CreatedAt); err != nil {
			return nil, err
		}
		entries = append(entries, e)
	}
	return entries, nil
}

func (db *DB) GetDailySummary(date string, targetKcal int) (types.DailySummary, error) {
	var s types.DailySummary
	err := db.conn.QueryRow(
		`SELECT date, total_consumed, total_burned, target_kcal, net_kcal FROM daily_summary WHERE date = ?`,
		date,
	).Scan(&s.Date, &s.TotalConsumed, &s.TotalBurned, &s.TargetKcal, &s.NetKcal)

	if err == sql.ErrNoRows {
		return types.DailySummary{
			Date:       date,
			TargetKcal: targetKcal,
		}, nil
	}

	s.TargetKcal = targetKcal
	return s, err
}

func (db *DB) updateDailySummary(date string) error {
	var consumed, burned int

	err := db.conn.QueryRow(
		`SELECT COALESCE(SUM(kcal), 0) FROM entries WHERE date = ? AND type = 'food'`, date,
	).Scan(&consumed)
	if err != nil {
		return err
	}

	err = db.conn.QueryRow(
		`SELECT COALESCE(SUM(kcal), 0) FROM entries WHERE date = ? AND type = 'exercise'`, date,
	).Scan(&burned)
	if err != nil {
		return err
	}

	net := consumed - burned

	_, err = db.conn.Exec(`
		INSERT INTO daily_summary (date, total_consumed, total_burned, target_kcal, net_kcal)
		VALUES (?, ?, ?, 0, ?)
		ON CONFLICT(date) DO UPDATE SET
			total_consumed = excluded.total_consumed,
			total_burned = excluded.total_burned,
			net_kcal = excluded.net_kcal
	`, date, consumed, burned, net)

	return err
}

func (db *DB) AddWeighIn(date string, weightKg float64) (types.WeighIn, error) {
	now := time.Now()
	if date == "" {
		date = now.Format("2006-01-02")
	}

	res, err := db.conn.Exec(
		`INSERT INTO weigh_ins (date, weight_kg) VALUES (?, ?)`,
		date, weightKg,
	)
	if err != nil {
		return types.WeighIn{}, err
	}

	id, _ := res.LastInsertId()
	return types.WeighIn{
		ID:        id,
		Date:      date,
		WeightKg:  weightKg,
		CreatedAt: now,
	}, nil
}

func (db *DB) GetLatestWeight() (float64, error) {
	var weight float64
	err := db.conn.QueryRow(
		`SELECT weight_kg FROM weigh_ins ORDER BY date DESC, created_at DESC LIMIT 1`,
	).Scan(&weight)
	if err == sql.ErrNoRows {
		return 0, nil
	}
	return weight, err
}

func (db *DB) GetWeightHistory() ([]types.WeighIn, error) {
	rows, err := db.conn.Query(
		`SELECT id, date, weight_kg, created_at FROM weigh_ins ORDER BY date DESC LIMIT 90`,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var weighIns []types.WeighIn
	for rows.Next() {
		var w types.WeighIn
		if err := rows.Scan(&w.ID, &w.Date, &w.WeightKg, &w.CreatedAt); err != nil {
			return nil, err
		}
		weighIns = append(weighIns, w)
	}
	return weighIns, nil
}

func (db *DB) GetHistory(days int) ([]types.DailySummary, error) {
	rows, err := db.conn.Query(
		`SELECT date, total_consumed, total_burned, target_kcal, net_kcal
		 FROM daily_summary ORDER BY date DESC LIMIT ?`,
		days,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var summaries []types.DailySummary
	for rows.Next() {
		var s types.DailySummary
		if err := rows.Scan(&s.Date, &s.TotalConsumed, &s.TotalBurned, &s.TargetKcal, &s.NetKcal); err != nil {
			return nil, err
		}
		summaries = append(summaries, s)
	}
	return summaries, nil
}

func (db *DB) SearchProducts(query string) ([]types.AutocompleteResult, error) {
	likeQuery := fmt.Sprintf("%%%s%%", query)
	rows, err := db.conn.Query(
		`SELECT name, brand, kcal_per_serving, serving_size, serving_unit
		 FROM products
		 WHERE name LIKE ? OR brand LIKE ?
		 ORDER BY last_used DESC
		 LIMIT 10`,
		likeQuery, likeQuery,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var results []types.AutocompleteResult
	for rows.Next() {
		var r types.AutocompleteResult
		if err := rows.Scan(&r.Name, &r.Brand, &r.KcalPerServing, &r.ServingSize, &r.ServingUnit); err != nil {
			return nil, err
		}
		results = append(results, r)
	}
	return results, nil
}

func (db *DB) FindProduct(name, brand string) (*types.Product, error) {
	var p types.Product
	err := db.conn.QueryRow(
		`SELECT id, name, brand, serving_size, serving_unit, kcal_per_serving, source, created_at, last_used
		 FROM products WHERE LOWER(name) = LOWER(?) AND (brand = '' OR LOWER(brand) = LOWER(?))
		 LIMIT 1`,
		name, brand,
	).Scan(&p.ID, &p.Name, &p.Brand, &p.ServingSize, &p.ServingUnit, &p.KcalPerServing, &p.Source, &p.CreatedAt, &p.LastUsed)

	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	// Update last_used
	db.conn.Exec(`UPDATE products SET last_used = CURRENT_TIMESTAMP WHERE id = ?`, p.ID)

	return &p, nil
}

func (db *DB) SaveProduct(p types.Product) error {
	_, err := db.conn.Exec(
		`INSERT INTO products (name, brand, serving_size, serving_unit, kcal_per_serving, source)
		 VALUES (?, ?, ?, ?, ?, ?)`,
		p.Name, p.Brand, p.ServingSize, p.ServingUnit, p.KcalPerServing, p.Source,
	)
	return err
}
