package store

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

// WriteFactAboutSubject is WriteFact plus a free-text subject the fact is
// about (e.g. "the espresso machine", "Kate"). If subject is non-empty the
// row starts life at ResolutionPending and the fact-enrichment worker
// (cmd/mavend/factenrichment.go) later resolves it against Nexus into
// EntityID. Facts with no subject are ordinary — ResolutionState stays
// ResolutionNone and no enrichment work is queued for them.
func (s *Store) WriteFactAboutSubject(ctx context.Context, ts time.Time, kind FactKind, key, subject, value, source string, confidence float64, voidsID sql.NullInt64) (int64, error) {
	if confidence <= 0.0 || confidence > 1.0 {
		return 0, fmt.Errorf("%w: %f", ErrConfidence, confidence)
	}
	if voidsID.Valid {
		var ok int64
		err := s.db.QueryRowContext(ctx, "SELECT 1 FROM facts WHERE id = ?", voidsID.Int64).Scan(&ok)
		if errors.Is(err, sql.ErrNoRows) {
			return 0, fmt.Errorf("%w: id=%d", ErrVoidsMissing, voidsID.Int64)
		}
		if err != nil {
			return 0, fmt.Errorf("voids lookup: %w", err)
		}
	}
	state := ResolutionNone
	if subject != "" {
		state = ResolutionPending
	}
	res, err := s.db.ExecContext(ctx,
		`INSERT INTO facts (ts, kind, key, value, source, confidence, voids_id, subject, resolution_state)
		 VALUES (?,?,?,?,?,?,?,?,?)`,
		ts.UnixMilli(), string(kind), key, value, source, confidence, voidsID, subject, string(state))
	if err != nil {
		return 0, fmt.Errorf("write fact about subject: %w", err)
	}
	id, err := res.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("last insert id: %w", err)
	}
	return id, nil
}

// PendingFactResolutions returns up to limit facts awaiting entity
// resolution (ResolutionPending), oldest first — the enrichment worker's
// work queue. Voided facts are included: a corrected fact's subject still
// deserves resolution so history stays queryable by entity.
func (s *Store) PendingFactResolutions(ctx context.Context, limit int) ([]Fact, error) {
	rows, err := s.db.QueryContext(ctx, `
		SELECT id, ts, kind, key, value, source, confidence, voids_id, subject, entity_id, resolution_state
		  FROM facts
		 WHERE resolution_state = ?
		 ORDER BY id ASC
		 LIMIT ?`, string(ResolutionPending), limit)
	if err != nil {
		return nil, fmt.Errorf("pending fact resolutions: %w", err)
	}
	defer rows.Close()
	var out []Fact
	for rows.Next() {
		f, err := scanFactWithEntity(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, f)
	}
	return out, rows.Err()
}

// ResolveFactEntity records the outcome of resolving a pending fact's
// subject against Nexus. entityID is ignored (left NULL) unless state is
// ResolutionResolved — an ambiguous or not-found result must not leave a
// stale/guessed entity_id behind.
func (s *Store) ResolveFactEntity(ctx context.Context, factID int64, entityID string, state FactResolutionState) error {
	var idArg sql.NullString
	if state == ResolutionResolved {
		idArg = sql.NullString{String: entityID, Valid: entityID != ""}
	}
	_, err := s.db.ExecContext(ctx,
		`UPDATE facts SET entity_id = ?, resolution_state = ? WHERE id = ?`,
		idArg, string(state), factID)
	if err != nil {
		return fmt.Errorf("resolve fact entity: %w", err)
	}
	return nil
}

// FactsByEntity returns facts resolved to the given Nexus entity_id, newest
// first, up to limit. Only ResolutionResolved rows carry an entity_id so
// this naturally excludes pending/ambiguous/not_found rows.
func (s *Store) FactsByEntity(ctx context.Context, entityID string, limit int) ([]Fact, error) {
	rows, err := s.db.QueryContext(ctx, `
		SELECT id, ts, kind, key, value, source, confidence, voids_id, subject, entity_id, resolution_state
		  FROM facts
		 WHERE entity_id = ?
		 ORDER BY ts DESC, id DESC
		 LIMIT ?`, entityID, limit)
	if err != nil {
		return nil, fmt.Errorf("facts by entity: %w", err)
	}
	defer rows.Close()
	var out []Fact
	for rows.Next() {
		f, err := scanFactWithEntity(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, f)
	}
	return out, rows.Err()
}

func scanFactWithEntity(r rowScanner) (Fact, error) {
	var f Fact
	var tsMilli int64
	var kind string
	var voids sql.NullInt64
	var state string
	if err := r.Scan(&f.ID, &tsMilli, &kind, &f.Key, &f.Value, &f.Source, &f.Confidence, &voids,
		&f.Subject, &f.EntityID, &state); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return Fact{}, ErrNoFact
		}
		return Fact{}, err
	}
	f.Ts = time.UnixMilli(tsMilli).UTC()
	f.Kind = FactKind(kind)
	f.VoidsID = voids
	f.ResolutionState = FactResolutionState(state)
	return f, nil
}
