package database

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"Engine/internal/model"
)

// SQLite stores timestamps as TEXT in the format written by
// strftime('%Y-%m-%dT%H:%M:%SZ','now').
// modernc.org/sqlite does not auto-convert TEXT → time.Time, so we scan
// into string and parse at the repo boundary with parseTime().

const sqliteTimeLayout = "2006-01-02T15:04:05Z"

func parseTime(s string) (time.Time, error) {
	t, err := time.Parse(sqliteTimeLayout, s)
	if err != nil {
		// fallback: SQLite can also produce "2006-01-02 15:04:05"
		t, err = time.Parse("2006-01-02 15:04:05", s)
	}
	return t, err
}

// scanBudget scans a budgets row into model.Budget.
// Accepts the 12 columns in SELECT order:
//
//	id, fiscal_year, fiscal_period, version, department_id, gl_account_id,
//	amount, currency, notes, created_by, created_at, updated_at
func scanBudget(row interface {
	Scan(dest ...any) error
}) (*model.Budget, error) {
	var b model.Budget
	var createdAt, updatedAt string

	err := row.Scan(
		&b.ID, &b.FiscalYear, &b.FiscalPeriod, &b.Version,
		&b.DepartmentID, &b.GLAccountID, &b.Amount, &b.Currency,
		&b.Notes, &b.CreatedBy, &createdAt, &updatedAt,
	)
	if err != nil {
		return nil, err
	}

	if b.CreatedAt, err = parseTime(createdAt); err != nil {
		return nil, fmt.Errorf("parse created_at %q: %w", createdAt, err)
	}
	if b.UpdatedAt, err = parseTime(updatedAt); err != nil {
		return nil, fmt.Errorf("parse updated_at %q: %w", updatedAt, err)
	}

	return &b, nil
}

// ── BudgetRepo ────────────────────────────────────────────────────────────────

type BudgetRepo struct {
	db *sql.DB
}

func NewBudgetRepo(db *sql.DB) *BudgetRepo {
	return &BudgetRepo{db: db}
}

const budgetSelectCols = `
	id, fiscal_year, fiscal_period, version, department_id, gl_account_id,
	amount, currency, notes, created_by, created_at, updated_at`

func (r *BudgetRepo) Create(ctx context.Context, req model.CreateBudgetRequest) (*model.Budget, error) {
	// ON CONFLICT upsert: if the unique key (year, period, version, dept, gl) already
	// exists, update the mutable fields instead of failing.
	// Makes the endpoint idempotent — safe for repeated test runs and re-imports.
	res, err := r.db.ExecContext(ctx, `
		INSERT INTO budgets
			(fiscal_year, fiscal_period, version, department_id, gl_account_id,
			 amount, currency, notes, created_by)
		VALUES (?,?,?,?,?,?,?,?,?)
		ON CONFLICT(fiscal_year, fiscal_period, version, department_id, gl_account_id)
		DO UPDATE SET
			amount     = excluded.amount,
			currency   = excluded.currency,
			notes      = excluded.notes,
			updated_at = strftime('%Y-%m-%dT%H:%M:%SZ','now')`,
		req.FiscalYear, req.FiscalPeriod, req.Version,
		req.DepartmentID, req.GLAccountID, req.Amount,
		req.Currency, req.Notes, req.CreatedBy,
	)
	if err != nil {
		return nil, fmt.Errorf("create budget: %w", err)
	}

	// LastInsertId returns the existing row id on a conflict branch in SQLite.
	id, err := res.LastInsertId()
	if err != nil {
		return nil, fmt.Errorf("last insert id: %w", err)
	}

	row := r.db.QueryRowContext(ctx,
		`SELECT `+budgetSelectCols+` FROM budgets WHERE id = ?`, id)
	b, err := scanBudget(row)
	if err != nil {
		return nil, fmt.Errorf("fetch created budget: %w", err)
	}
	return b, nil
}

func (r *BudgetRepo) Update(ctx context.Context, id int, req model.UpdateBudgetRequest) (*model.Budget, error) {
	_, err := r.db.ExecContext(ctx, `
		UPDATE budgets
		SET version=?, amount=?, notes=?,
		    updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now')
		WHERE id=?`,
		req.Version, req.Amount, req.Notes, id,
	)
	if err != nil {
		return nil, fmt.Errorf("update budget: %w", err)
	}

	row := r.db.QueryRowContext(ctx,
		`SELECT`+budgetSelectCols+`FROM budgets WHERE id = ?`, id)
	b, err := scanBudget(row)
	if err != nil {
		return nil, fmt.Errorf("fetch updated budget: %w", err)
	}
	return b, nil
}

func (r *BudgetRepo) Delete(ctx context.Context, id int) error {
	_, err := r.db.ExecContext(ctx, `DELETE FROM budgets WHERE id = ?`, id)
	if err != nil {
		return fmt.Errorf("delete budget: %w", err)
	}
	return nil
}

func (r *BudgetRepo) GetByID(ctx context.Context, id int) (*model.Budget, error) {
	row := r.db.QueryRowContext(ctx,
		`SELECT`+budgetSelectCols+`FROM budgets WHERE id = ?`, id)
	b, err := scanBudget(row)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get budget: %w", err)
	}
	return b, nil
}

func (r *BudgetRepo) ListByPeriod(ctx context.Context, fiscalYear, fiscalPeriod int, version model.BudgetVersion) ([]model.Budget, error) {
	rows, err := r.db.QueryContext(ctx,
		`SELECT`+budgetSelectCols+`
		 FROM budgets
		 WHERE fiscal_year=? AND fiscal_period=? AND version=?
		 ORDER BY department_id, gl_account_id`,
		fiscalYear, fiscalPeriod, version,
	)
	if err != nil {
		return nil, fmt.Errorf("list budgets: %w", err)
	}
	defer rows.Close()

	var budgets []model.Budget
	for rows.Next() {
		b, err := scanBudget(rows)
		if err != nil {
			return nil, fmt.Errorf("scan budget row: %w", err)
		}
		budgets = append(budgets, *b)
	}
	return budgets, rows.Err()
}

// List returns BudgetRow slice joined with gl_accounts — used by VarianceService.
// Filters by fiscal_year, fiscal_period, version, and optionally dept_code
// (matched against departments.code via JOIN).
func (r *BudgetRepo) List(
	ctx context.Context,
	fiscalYear, fiscalPeriod int,
	deptCode string,
	version model.BudgetVersion,
) ([]model.BudgetRow, error) {
	q := `
		SELECT
			g.code,
			g.description,
			g.type,
			g.favour_high,
			b.amount,
			b.currency
		FROM budgets b
		JOIN gl_accounts  g ON g.id = b.gl_account_id
		JOIN departments  d ON d.id = b.department_id
		WHERE b.fiscal_year   = ?
		  AND b.fiscal_period = ?
		  AND b.version       = ?
		  AND d.code          = ?
		ORDER BY g.code`

	rows, err := r.db.QueryContext(ctx, q,
		fiscalYear, fiscalPeriod, version, deptCode)
	if err != nil {
		return nil, fmt.Errorf("budget list: %w", err)
	}
	defer rows.Close()

	var out []model.BudgetRow
	for rows.Next() {
		var br model.BudgetRow
		var favourHigh int
		if err := rows.Scan(
			&br.GLCode, &br.GLDescription, &br.GLType,
			&favourHigh, &br.Amount, &br.Currency,
		); err != nil {
			return nil, fmt.Errorf("budget list scan: %w", err)
		}
		br.FavourHigh = favourHigh == 1
		out = append(out, br)
	}
	return out, rows.Err()
}
