package database

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

// ── Domain types ──────────────────────────────────────────────────────────────

type Department struct {
	ID         int    `json:"id"`
	Code       string `json:"code"`
	Name       string `json:"name"`
	CostCenter string `json:"cost_center"`
	Active     bool   `json:"active"`
	CreatedAt  string `json:"created_at"`
}

type GLAccount struct {
	ID          int    `json:"id"`
	Code        string `json:"code"`
	Description string `json:"description"`
	Type        string `json:"type"`        // revenue | cogs | opex | capex | headcount
	FavourHigh  bool   `json:"favour_high"` // true = over-budget is good (revenue accounts)
	Active      bool   `json:"active"`
}

// ── ReferenceRepo ─────────────────────────────────────────────────────────────

type ReferenceRepo struct {
	db *sql.DB
}

func NewReferenceRepo(db *sql.DB) *ReferenceRepo {
	return &ReferenceRepo{db: db}
}

// ── Department operations ─────────────────────────────────────────────────────

func (r *ReferenceRepo) CreateDepartment(ctx context.Context, d Department) (*Department, error) {
	res, err := r.db.ExecContext(ctx, `
		INSERT INTO departments (code, name, cost_center, active)
		VALUES (?, ?, ?, ?)
		ON CONFLICT(code) DO UPDATE
		    SET name        = excluded.name,
		        cost_center = excluded.cost_center,
		        active      = excluded.active`,
		d.Code, d.Name, d.CostCenter, boolToInt(d.Active),
	)
	if err != nil {
		return nil, fmt.Errorf("upsert department: %w", err)
	}

	id, err := res.LastInsertId()
	if err != nil || id == 0 {
		// ON CONFLICT branch — fetch existing row
		return r.getDepartmentByCode(ctx, d.Code)
	}
	d.ID = int(id)
	return &d, nil
}

func (r *ReferenceRepo) getDepartmentByCode(ctx context.Context, code string) (*Department, error) {
	var d Department
	var active int
	err := r.db.QueryRowContext(ctx,
		`SELECT id, code, name, cost_center, active, created_at FROM departments WHERE code = ?`, code,
	).Scan(&d.ID, &d.Code, &d.Name, &d.CostCenter, &active, &d.CreatedAt)
	if err != nil {
		return nil, fmt.Errorf("fetch department by code: %w", err)
	}
	d.Active = active == 1
	return &d, nil
}

func (r *ReferenceRepo) ListDepartments(ctx context.Context) ([]Department, error) {
	rows, err := r.db.QueryContext(ctx,
		`SELECT id, code, name, cost_center, active, created_at
		 FROM departments ORDER BY code`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var depts []Department
	for rows.Next() {
		var d Department
		var active int
		if err := rows.Scan(&d.ID, &d.Code, &d.Name, &d.CostCenter, &active, &d.CreatedAt); err != nil {
			return nil, err
		}
		d.Active = active == 1
		depts = append(depts, d)
	}
	return depts, rows.Err()
}

func (r *ReferenceRepo) DeleteDepartment(ctx context.Context, id int) error {
	_, err := r.db.ExecContext(ctx, `DELETE FROM departments WHERE id = ?`, id)
	return err
}

// ── GL Account operations ─────────────────────────────────────────────────────

func (r *ReferenceRepo) CreateGLAccount(ctx context.Context, a GLAccount) (*GLAccount, error) {
	res, err := r.db.ExecContext(ctx, `
		INSERT INTO gl_accounts (code, description, type, favour_high, active)
		VALUES (?, ?, ?, ?, ?)
		ON CONFLICT(code) DO UPDATE
		    SET description = excluded.description,
		        type        = excluded.type,
		        favour_high = excluded.favour_high,
		        active      = excluded.active`,
		a.Code, a.Description, a.Type, boolToInt(a.FavourHigh), boolToInt(a.Active),
	)
	if err != nil {
		return nil, fmt.Errorf("upsert gl_account: %w", err)
	}

	id, err := res.LastInsertId()
	if err != nil || id == 0 {
		return r.getGLAccountByCode(ctx, a.Code)
	}
	a.ID = int(id)
	return &a, nil
}

func (r *ReferenceRepo) getGLAccountByCode(ctx context.Context, code string) (*GLAccount, error) {
	var a GLAccount
	var favourHigh, active int
	err := r.db.QueryRowContext(ctx,
		`SELECT id, code, description, type, favour_high, active FROM gl_accounts WHERE code = ?`, code,
	).Scan(&a.ID, &a.Code, &a.Description, &a.Type, &favourHigh, &active)
	if err != nil {
		return nil, fmt.Errorf("fetch gl_account by code: %w", err)
	}
	a.FavourHigh = favourHigh == 1
	a.Active = active == 1
	return &a, nil
}

func (r *ReferenceRepo) ListGLAccounts(ctx context.Context) ([]GLAccount, error) {
	rows, err := r.db.QueryContext(ctx,
		`SELECT id, code, description, type, favour_high, active
		 FROM gl_accounts ORDER BY code`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var accts []GLAccount
	for rows.Next() {
		var a GLAccount
		var favourHigh, active int
		if err := rows.Scan(&a.ID, &a.Code, &a.Description, &a.Type, &favourHigh, &active); err != nil {
			return nil, err
		}
		a.FavourHigh = favourHigh == 1
		a.Active = active == 1
		accts = append(accts, a)
	}
	return accts, rows.Err()
}

func (r *ReferenceRepo) DeleteGLAccount(ctx context.Context, id int) error {
	_, err := r.db.ExecContext(ctx, `DELETE FROM gl_accounts WHERE id = ?`, id)
	return err
}

// ── helpers ───────────────────────────────────────────────────────────────────

func boolToInt(b bool) int {
	if b {
		return 1
	}
	return 0
}
