package qslcard

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

// Record is one stored template row. JSON holds the template document;
// callers Parse/Validate it as needed (the list view only needs metadata).
type Record struct {
	ID        int64     `json:"id"`
	Name      string    `json:"name"`
	ProfileID *int64    `json:"profile_id,omitempty"`
	JSON      string    `json:"json"`
	IsDefault bool      `json:"is_default"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

// Repo accesses the qsl_templates table.
type Repo struct{ db *sql.DB }

// NewRepo builds a template repo on the given connection.
func NewRepo(db *sql.DB) *Repo { return &Repo{db: db} }

const repoCols = `id, name, profile_id, json, is_default, created_at, updated_at`

// List returns all templates, defaults first then newest first.
func (r *Repo) List(ctx context.Context) ([]Record, error) {
	rows, err := r.db.QueryContext(ctx, `SELECT `+repoCols+`
		FROM qsl_templates ORDER BY is_default DESC, id DESC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []Record
	for rows.Next() {
		rec, err := scanRecord(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, rec)
	}
	return out, rows.Err()
}

// ListFor returns the templates VISIBLE to a profile: its own plus any shared
// (NULL-profile) ones — so each profile sees and defaults independently instead
// of one global list. Defaults first, then newest first.
func (r *Repo) ListFor(ctx context.Context, profileID int64) ([]Record, error) {
	rows, err := r.db.QueryContext(ctx, `SELECT `+repoCols+`
		FROM qsl_templates WHERE profile_id = ? OR profile_id IS NULL
		ORDER BY is_default DESC, id DESC`, profileID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []Record
	for rows.Next() {
		rec, err := scanRecord(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, rec)
	}
	return out, rows.Err()
}

// Get returns one template by ID, or sql.ErrNoRows if missing.
func (r *Repo) Get(ctx context.Context, id int64) (Record, error) {
	row := r.db.QueryRowContext(ctx, `SELECT `+repoCols+`
		FROM qsl_templates WHERE id = ?`, id)
	return scanRecord(row)
}

// Save upserts a template. rec.ID == 0 means "create"; the generated ID is
// written back so the caller can place the template's asset folder.
func (r *Repo) Save(ctx context.Context, rec *Record) error {
	if rec.Name == "" {
		return fmt.Errorf("template name required")
	}
	if rec.JSON == "" {
		return fmt.Errorf("template json required")
	}
	now := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
	if rec.ID == 0 {
		res, err := r.db.ExecContext(ctx, `
			INSERT INTO qsl_templates (name, profile_id, json, is_default, created_at, updated_at)
			VALUES (?,?,?,?,?,?)`,
			rec.Name, nullableID(rec.ProfileID), rec.JSON, boolInt(rec.IsDefault), now, now)
		if err != nil {
			return fmt.Errorf("insert template: %w", err)
		}
		id, err := res.LastInsertId()
		if err != nil {
			return fmt.Errorf("insert template id: %w", err)
		}
		rec.ID = id
		return nil
	}
	_, err := r.db.ExecContext(ctx, `
		UPDATE qsl_templates SET name = ?, profile_id = ?, json = ?, updated_at = ?
		WHERE id = ?`,
		rec.Name, nullableID(rec.ProfileID), rec.JSON, now, rec.ID)
	if err != nil {
		return fmt.Errorf("update template: %w", err)
	}
	return nil
}

// Delete removes a template row. The caller removes the asset folder.
func (r *Repo) Delete(ctx context.Context, id int64) error {
	_, err := r.db.ExecContext(ctx, `DELETE FROM qsl_templates WHERE id = ?`, id)
	return err
}

// SetDefault marks one template as the default for its profile scope,
// clearing the flag on every other template of the same scope.
func (r *Repo) SetDefault(ctx context.Context, id int64) error {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback() //nolint:errcheck
	var profileID sql.NullInt64
	if err := tx.QueryRowContext(ctx,
		`SELECT profile_id FROM qsl_templates WHERE id = ?`, id).Scan(&profileID); err != nil {
		return err
	}
	if profileID.Valid {
		_, err = tx.ExecContext(ctx, `UPDATE qsl_templates SET is_default = 0 WHERE profile_id = ?`, profileID.Int64)
	} else {
		_, err = tx.ExecContext(ctx, `UPDATE qsl_templates SET is_default = 0 WHERE profile_id IS NULL`)
	}
	if err != nil {
		return err
	}
	if _, err := tx.ExecContext(ctx, `UPDATE qsl_templates SET is_default = 1 WHERE id = ?`, id); err != nil {
		return err
	}
	return tx.Commit()
}

// DefaultFor returns the best template for a profile: its own default, then
// any profile-less default, then the newest template. sql.ErrNoRows if none.
func (r *Repo) DefaultFor(ctx context.Context, profileID int64) (Record, error) {
	// Only the profile's OWN templates or shared (NULL) ones — never another
	// profile's, so a profile with no default doesn't inherit some other
	// profile's newest template (the "default looked global" bug).
	row := r.db.QueryRowContext(ctx, `SELECT `+repoCols+` FROM qsl_templates
		WHERE profile_id = ? OR profile_id IS NULL
		ORDER BY (is_default = 1 AND profile_id = ?) DESC,
		         (is_default = 1 AND profile_id IS NULL) DESC,
		         (profile_id = ?) DESC,
		         id DESC LIMIT 1`, profileID, profileID, profileID)
	return scanRecord(row)
}

// ----- helpers -----

type scannable interface{ Scan(dest ...any) error }

func scanRecord(row scannable) (Record, error) {
	var rec Record
	var profileID sql.NullInt64
	var isDefault int
	var createdAt, updatedAt string
	if err := row.Scan(&rec.ID, &rec.Name, &profileID, &rec.JSON, &isDefault, &createdAt, &updatedAt); err != nil {
		return rec, err
	}
	if profileID.Valid {
		v := profileID.Int64
		rec.ProfileID = &v
	}
	rec.IsDefault = isDefault == 1
	rec.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt)
	rec.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updatedAt)
	return rec, nil
}

func nullableID(p *int64) any {
	if p == nil {
		return nil
	}
	return *p
}

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