package store

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

// CertKind enumerates the kinds of certs orca tracks. 'ca' is the
// cluster's internal CA; 'server' is a per-node server cert.
type CertKind string

const (
	CertKindCA     CertKind = "ca"
	CertKindServer CertKind = "server"
)

// Cert is the in-memory representation of a row in the `certs` table.
type Cert struct {
	ID          string    `json:"id"`
	Kind        CertKind  `json:"kind"`
	NodeID      string    `json:"node_id"`
	SerialHex   string    `json:"serial_hex"`
	SubjectCN   string    `json:"subject_cn"`
	IssuerCN    string    `json:"issuer_cn"`
	NotBefore   time.Time `json:"not_before"`
	NotAfter    time.Time `json:"not_after"`
	Fingerprint string    `json:"fingerprint"`
	SourcePath  string    `json:"source_path,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
}

// CertRepo is a CRUD wrapper around the `certs` table.
type CertRepo struct {
	db *sql.DB
}

func NewCertRepo(db *sql.DB) *CertRepo {
	return &CertRepo{db: db}
}

// Insert persists a new cert. Fills CreatedAt to now() if zero. The caller
// is responsible for setting ID, SerialHex, Fingerprint, etc.
func (r *CertRepo) Insert(ctx context.Context, c *Cert) error {
	if c == nil {
		return errors.New("CertRepo.Insert: nil cert")
	}
	if c.ID == "" {
		return errors.New("CertRepo.Insert: ID is required")
	}
	if c.Kind == "" {
		return errors.New("CertRepo.Insert: Kind is required")
	}
	if c.CreatedAt.IsZero() {
		c.CreatedAt = time.Now().UTC()
	}
	_, err := r.db.ExecContext(ctx,
		`INSERT INTO certs (id, kind, node_id, serial_hex, subject_cn, issuer_cn, not_before, not_after, fingerprint, source_path, created_at)
		 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		c.ID, string(c.Kind), c.NodeID, c.SerialHex, c.SubjectCN, c.IssuerCN,
		c.NotBefore, c.NotAfter, c.Fingerprint, c.SourcePath, c.CreatedAt)
	if err != nil {
		return fmt.Errorf("CertRepo.Insert: %w", err)
	}
	return nil
}

// Get returns a single cert by ID. Returns ErrNotFound if absent.
func (r *CertRepo) Get(ctx context.Context, id string) (*Cert, error) {
	row := r.db.QueryRowContext(ctx,
		`SELECT id, kind, node_id, serial_hex, subject_cn, issuer_cn, not_before, not_after, fingerprint, source_path, created_at FROM certs WHERE id = ?`, id)
	return scanCert(row)
}

// List returns all certs ordered by created_at DESC. Use ListByNode /
// LatestForKind for filtered queries.
func (r *CertRepo) List(ctx context.Context) ([]*Cert, error) {
	rows, err := r.db.QueryContext(ctx,
		`SELECT id, kind, node_id, serial_hex, subject_cn, issuer_cn, not_before, not_after, fingerprint, source_path, created_at FROM certs ORDER BY created_at DESC`)
	if err != nil {
		return nil, fmt.Errorf("CertRepo.List: %w", err)
	}
	defer rows.Close()
	var certs []*Cert
	for rows.Next() {
		c, err := scanCert(rows)
		if err != nil {
			return nil, err
		}
		certs = append(certs, c)
	}
	return certs, rows.Err()
}

// ListByNode returns certs belonging to a node (or matching node_id for the
// CA — CA rows use node_id = ”).
func (r *CertRepo) ListByNode(ctx context.Context, nodeID string) ([]*Cert, error) {
	rows, err := r.db.QueryContext(ctx,
		`SELECT id, kind, node_id, serial_hex, subject_cn, issuer_cn, not_before, not_after, fingerprint, source_path, created_at FROM certs WHERE node_id = ? ORDER BY created_at DESC`, nodeID)
	if err != nil {
		return nil, fmt.Errorf("CertRepo.ListByNode: %w", err)
	}
	defer rows.Close()
	var certs []*Cert
	for rows.Next() {
		c, err := scanCert(rows)
		if err != nil {
			return nil, err
		}
		certs = append(certs, c)
	}
	return certs, rows.Err()
}

// LatestForKind returns the most recent cert of the given kind for the given
// node. Returns ErrNotFound if none exists. nodeID may be empty to query
// the cluster-wide CA.
func (r *CertRepo) LatestForKind(ctx context.Context, nodeID string, kind CertKind) (*Cert, error) {
	row := r.db.QueryRowContext(ctx,
		`SELECT id, kind, node_id, serial_hex, subject_cn, issuer_cn, not_before, not_after, fingerprint, source_path, created_at FROM certs WHERE node_id = ? AND kind = ? ORDER BY created_at DESC LIMIT 1`,
		nodeID, string(kind))
	return scanCert(row)
}

// PruneOlderThan deletes certs beyond the most recent `keep` rows for
// (nodeID, kind), ordered by created_at DESC. Returns the number of
// rows deleted. `keep` must be > 0; values <= 0 are treated as 1.
func (r *CertRepo) PruneOlderThan(ctx context.Context, nodeID, kind string, keep int) (int64, error) {
	if keep <= 0 {
		keep = 1
	}
	// Two-step delete: first find the cutoff created_at, then delete
	// everything older. Done in a single transaction via ExecContext.
	// modernc/sqlite supports multiple statements in a single Exec only
	// via the "multi-statement" pragma; we use a subquery instead.
	res, err := r.db.ExecContext(ctx,
		`DELETE FROM certs WHERE node_id = ? AND kind = ? AND id NOT IN (
			SELECT id FROM certs WHERE node_id = ? AND kind = ?
			ORDER BY created_at DESC LIMIT ?
		)`,
		nodeID, kind, nodeID, kind, keep)
	if err != nil {
		return 0, fmt.Errorf("CertRepo.PruneOlderThan: %w", err)
	}
	n, _ := res.RowsAffected()
	return n, nil
}

// Delete removes a cert by ID. Returns ErrNotFound if no rows affected.
func (r *CertRepo) Delete(ctx context.Context, id string) error {
	res, err := r.db.ExecContext(ctx, `DELETE FROM certs WHERE id = ?`, id)
	if err != nil {
		return fmt.Errorf("CertRepo.Delete: %w", err)
	}
	rows, _ := res.RowsAffected()
	if rows == 0 {
		return ErrNotFound
	}
	return nil
}

func scanCert(s scanner) (*Cert, error) {
	var (
		c       Cert
		kindStr string
	)
	err := s.Scan(&c.ID, &kindStr, &c.NodeID, &c.SerialHex, &c.SubjectCN, &c.IssuerCN,
		&c.NotBefore, &c.NotAfter, &c.Fingerprint, &c.SourcePath, &c.CreatedAt)
	if err == sql.ErrNoRows {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("scan cert: %w", err)
	}
	c.Kind = CertKind(kindStr)
	return &c, nil
}
