-- name: ListSignals :many
SELECT sig.entity_id, se.slug, sig.kind, sig.severity, sig.state,
       te.slug AS target_slug, sig.check_id, sig.evidence, sig.likely_cause,
       sig.occurrence_count, sig.flap_count, sig.hold_down_until,
       sig.mute_until, sig.first_seen_at, sig.last_seen_at
FROM signals sig
JOIN entities se ON se.id = sig.entity_id
LEFT JOIN entities te ON te.id = sig.target_entity_id
WHERE (sqlc.narg('state')::text IS NULL OR sig.state = sqlc.narg('state'))
  AND (sqlc.narg('severity')::text IS NULL OR sig.severity = sqlc.narg('severity'))
  AND (sqlc.narg('target')::text IS NULL OR te.slug = sqlc.narg('target'))
  AND (sqlc.narg('kind')::text IS NULL OR sig.kind = sqlc.narg('kind'))
  AND (sqlc.narg('cursor')::text IS NULL OR se.slug > sqlc.narg('cursor'))
ORDER BY se.slug
LIMIT sqlc.arg('lim');

-- name: GetIdempotentResponse :one
SELECT response_code, response_body, request_hash FROM idempotency_keys
WHERE actor = $1 AND key = $2;

-- name: PutIdempotentResponse :exec
INSERT INTO idempotency_keys (actor, key, request_hash, response_code, response_body)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (actor, key) DO NOTHING;

-- name: InsertAuditEntry :exec
INSERT INTO audit_log (actor_type, actor_id, action, entity_id, method, path,
                       status_code, detail, source_ip, correlation_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);

-- name: InsertEvent :one
INSERT INTO events (type, entity_id, severity, source, data, correlation_id)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, ts;

-- name: ListEvents :many
SELECT id, ts, type, entity_id, severity, source, data, correlation_id
FROM events
WHERE (sqlc.narg('type')::text IS NULL OR type = sqlc.narg('type'))
  AND (sqlc.narg('entity_id')::uuid IS NULL OR entity_id = sqlc.narg('entity_id'))
  AND (sqlc.narg('severity')::text IS NULL OR severity = sqlc.narg('severity'))
  AND (sqlc.narg('correlation_id')::text IS NULL OR correlation_id = sqlc.narg('correlation_id'))
  AND (sqlc.narg('from_ts')::timestamptz IS NULL OR ts >= sqlc.narg('from_ts'))
  AND (sqlc.narg('to_ts')::timestamptz IS NULL OR ts <= sqlc.narg('to_ts'))
  AND (sqlc.narg('before_id')::bigint IS NULL OR id < sqlc.narg('before_id'))
ORDER BY id DESC
LIMIT sqlc.arg('lim');

-- name: ListEventsAfter :many
SELECT id, ts, type, entity_id, severity, source, data, correlation_id
FROM events WHERE id > $1 ORDER BY id ASC LIMIT $2;

-- =====================================================================
-- Phase 3 queries
-- =====================================================================

-- name: ListEnabledCheckDefs :many
-- Enabled AND due. interval_s used to be selected but never filtered on, so
-- every check ran on every 30s pass and the declared intervals meant nothing.
-- NULL last_run_at = never run = due now.
SELECT cd.entity_id, cd.target_id, cd.target_type, cd.kind, cd.config,
       cd.interval_s, cd.timeout_s, cd.zone, cd.enabled, cd.updated_at,
       e.slug AS entity_slug
FROM check_defs cd
JOIN entities e ON e.id = cd.entity_id
WHERE cd.enabled = true
  AND (cd.last_run_at IS NULL
       OR cd.last_run_at <= now() - make_interval(secs => cd.interval_s));

-- name: MarkCheckRun :exec
UPDATE check_defs SET last_run_at = now(), last_health = $2 WHERE entity_id = $1;

-- name: WorstHealthForTarget :one
-- An entity is as healthy as its unhealthiest check. Checks that have not run
-- yet (last_health IS NULL) are ignored rather than counted as unknown, so a
-- newly added check does not drag a known-good entity down before it has
-- produced a verdict.
SELECT COALESCE(
  (SELECT last_health FROM check_defs
   WHERE enabled AND target_id = $1 AND last_health IS NOT NULL
   ORDER BY CASE last_health
     WHEN 'down' THEN 0 WHEN 'degraded' THEN 1 WHEN 'stale' THEN 2
     WHEN 'unknown' THEN 3 ELSE 4 END
   LIMIT 1),
  'unknown')::text AS health;

-- name: GetCheckDef :one
SELECT * FROM check_defs WHERE entity_id = $1;

-- name: InsertCheckDef :exec
INSERT INTO check_defs (entity_id, target_id, target_type, kind, config, interval_s, timeout_s, zone, enabled)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);

-- name: UpdateCheckDef :exec
UPDATE check_defs SET kind = $2, config = $3, interval_s = $4, timeout_s = $5,
    target_id = $6, target_type = $7, zone = $8, enabled = $9, updated_at = now()
WHERE entity_id = $1;

-- name: UpsertSignal :one
INSERT INTO signals (entity_id, kind, severity, target_entity_id, check_id, evidence, likely_cause, state)
VALUES ($1, $2, $3, $4, $5, $6, $7, 'raised')
ON CONFLICT (target_entity_id, kind) WHERE state NOT IN ('resolved','failed')
DO UPDATE SET occurrence_count = signals.occurrence_count + 1,
              last_seen_at = now(),
              evidence = EXCLUDED.evidence,
              updated_at = now()
RETURNING *;

-- name: GetOpenSignalsForAutoAct :many
-- Signals with auto-act classifications that haven't been executed yet
SELECT s.*, c.entity_id AS classification_id, c.action, c.risk_class, c.route,
       c.blast_radius, c.correlation_id, c.reasoning
FROM classifications c
JOIN signals s ON s.entity_id = c.signal_entity_id
LEFT JOIN executions e ON e.classification_id = c.entity_id
WHERE c.route = 'auto-act'
  AND e.entity_id IS NULL
  AND (s.hold_down_until IS NULL OR s.hold_down_until < now())
  AND (s.mute_until IS NULL OR s.mute_until < now())
ORDER BY s.last_seen_at ASC
LIMIT $1;

-- name: ListClassifications :many
SELECT c.entity_id, c.signal_entity_id, c.target_entity_id, c.action,
       c.recommended_action, c.risk_class, c.route, c.blast_radius,
       c.pattern_confidence, c.skill_id, c.autonomy_check, c.reasoning,
       c.correlation_id, c.created_at,
       e.slug AS target_slug
FROM classifications c
JOIN entities e ON e.id = c.target_entity_id
WHERE (sqlc.narg('route')::text IS NULL OR c.route = sqlc.narg('route'))
  AND (sqlc.narg('cursor')::text IS NULL OR e.slug > sqlc.narg('cursor'))
ORDER BY e.slug
LIMIT sqlc.arg('lim');

-- name: InsertExecution :exec
INSERT INTO executions (entity_id, classification_id, signal_entity_id,
    target_entity_id, action, risk_class, approval_id, agent_id,
    skill_id, skill_version, status, correlation_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'proposed', $11);

-- name: UpdateExecutionStatus :exec
UPDATE executions SET status = $2, result = $3, duration_ms = $4,
    verified = $5, started_at = COALESCE(started_at, now()),
    completed_at = CASE WHEN $2 IN ('completed','failed','cancelled') THEN now() ELSE completed_at END
WHERE entity_id = $1;

-- name: GetExecution :one
SELECT * FROM executions WHERE entity_id = $1;

-- name: ListExecutions :many
SELECT e.entity_id, e.classification_id, e.signal_entity_id, e.target_entity_id,
       e.action, e.risk_class, e.approval_id, e.agent_id,
       e.skill_id, e.skill_version, e.status, e.result, e.duration_ms,
       e.verified, e.correlation_id, e.started_at, e.completed_at, e.created_at,
       te.slug AS target_slug
FROM executions e
JOIN entities te ON te.id = e.target_entity_id
WHERE (sqlc.narg('status')::text IS NULL OR e.status = sqlc.narg('status'))
  AND (sqlc.narg('cursor')::text IS NULL OR te.slug > sqlc.narg('cursor'))
ORDER BY te.slug
LIMIT sqlc.arg('lim');

-- name: GetFeedbackAfterWatermark :many
SELECT f.entity_id, f.execution_id, f.outcome, f.observation, f.lesson,
       f.unexpected_side_effects, f.tags, f.created_at,
       e.action, e.risk_class, e.target_entity_id,
       et.name AS applies_type
FROM feedback f
JOIN executions e ON e.entity_id = f.execution_id
JOIN entities ent ON ent.id = e.target_entity_id
JOIN entity_types et ON et.name = ent.type
WHERE f.created_at > $1
ORDER BY f.created_at ASC;

-- name: UpsertPattern :exec
INSERT INTO patterns (entity_id, applies_type, action, pattern, confidence,
    evidence_count, success_count, failure_count, status, version)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'hypothesized', 1)
ON CONFLICT (applies_type, action)
DO UPDATE SET evidence_count = patterns.evidence_count + EXCLUDED.evidence_count,
    success_count = patterns.success_count + EXCLUDED.success_count,
    failure_count = patterns.failure_count + EXCLUDED.failure_count,
    updated_at = now();

-- name: GetPattern :one
SELECT * FROM patterns WHERE applies_type = $1 AND action = $2;

-- name: ListPatterns :many
SELECT p.* FROM patterns p
WHERE (sqlc.narg('status')::text IS NULL OR p.status = sqlc.narg('status'))
ORDER BY p.applies_type, p.action;

-- name: UpdatePatternStatus :exec
UPDATE patterns SET status = $2, version = version + 1,
    last_validated_at = CASE WHEN $2 = 'validated' THEN now() ELSE last_validated_at END
WHERE entity_id = $1;

-- name: UpdatePatternQuarantine :exec
UPDATE patterns SET quarantined = $2 WHERE entity_id = $1;

-- name: ListSkills :many
SELECT * FROM skills
WHERE (sqlc.narg('status')::text IS NULL OR status = sqlc.narg('status'))
ORDER BY name, version DESC;

-- name: UpdateSkillStatus :exec
UPDATE skills SET status = $2, last_used_at = now() WHERE entity_id = $1 AND version = $2;

-- name: InsertApproval :exec
INSERT INTO approvals (entity_id, subject_entity_id, action, risk_class, kind,
    payload, status, token_hash, expires_at)
VALUES ($1, $2, $3, $4, $5, $6, 'pending', $7, $8);

-- name: ListApprovals :many
SELECT a.*, e.slug AS subject_slug
FROM approvals a
JOIN entities e ON e.id = a.subject_entity_id
WHERE (sqlc.narg('status')::text IS NULL OR a.status = sqlc.narg('status'))
  AND (sqlc.narg('cursor')::text IS NULL OR e.slug > sqlc.narg('cursor'))
ORDER BY e.slug
LIMIT sqlc.arg('lim');

-- name: GetApprovalByID :one
SELECT * FROM approvals WHERE entity_id = $1;

-- name: UpdateApprovalStatus :exec
UPDATE approvals SET status = $2, decided_at = now(), decided_by = $3
WHERE entity_id = $1 AND status = 'pending';

-- name: GetAutonomySetting :one
SELECT value FROM autonomy_settings WHERE key = $1;

-- name: ListRiskClasses :many
SELECT * FROM risk_classes ORDER BY name;

-- name: ListApprovalRules :many
SELECT * FROM approval_rules ORDER BY entity_type, action;

-- name: InsertMetricSample :exec
INSERT INTO metric_samples (entity_id, metric, value, tags, ts)
VALUES ($1, $2, $3, $4, now());

-- name: QueryMetrics :many
SELECT time_bucket(sqlc.arg('bucket_interval')::interval, ts) AS bucket,
       entity_id, metric,
       ROUND(avg(value)::numeric, 2) AS avg_val,
       ROUND(min(value)::numeric, 2) AS min_val,
       ROUND(max(value)::numeric, 2) AS max_val
FROM metric_samples
WHERE entity_id = $1
  AND metric = $2
  AND ts > $3
GROUP BY bucket, entity_id, metric
ORDER BY bucket DESC;

-- name: UpsertEntityStatus :exec
INSERT INTO entity_status (entity_id, health, last_check_at, details)
VALUES ($1, $2, $3, $4)
ON CONFLICT (entity_id)
DO UPDATE SET health = EXCLUDED.health,
    last_check_at = EXCLUDED.last_check_at,
    details = EXCLUDED.details,
    updated_at = now();

-- name: GetEntityStatus :one
SELECT * FROM entity_status WHERE entity_id = $1;