"""DuckDB schema for the Nostr ingest pipeline.

Pure data — no Python logic, no imports. ``IngestDaemonSQL._ensure_schema``
runs every statement here on every boot; ``CREATE … IF NOT EXISTS`` makes
the bootstrap idempotent for both fresh and existing DB files.

Schema columns mirror what ``run_sql.md`` documents for the agent and what
``duckdb/schema.sql`` historically held — drift here is a hard error to
catch early. Update both files in lockstep when a column changes shape.
"""

from __future__ import annotations


# CREATE TABLE / CREATE INDEX statements run on every boot. ``IF NOT EXISTS``
# makes them idempotent; an existing DB sees no-ops, a fresh file gets the
# full schema in one shot.
BOOTSTRAP_DDL: tuple[str, ...] = (
    "CREATE TABLE IF NOT EXISTS pubkeys ("
    " hex VARCHAR PRIMARY KEY,"
    " relay_list_ts BIGINT)",

    "CREATE TABLE IF NOT EXISTS relays ("
    " url VARCHAR PRIMARY KEY,"
    " status VARCHAR,"
    " last_checked_at BIGINT,"
    " last_error VARCHAR,"
    " active BOOLEAN,"
    " consecutive_failures BIGINT DEFAULT 0,"
    " discovery_picks BIGINT DEFAULT 0,"
    " discovery_rejections BIGINT DEFAULT 0,"
    " invalid_events_count BIGINT DEFAULT 0)",

    # Forensics log for events that failed `_verify_event` (id mismatch, bad
    # signature, or malformed fields). Stored verbatim with the source relay
    # so we can investigate which relays are sending forged events. Append-only;
    # no PK constraint — same forged id may arrive from multiple relays and
    # we want to see all of them.
    "CREATE TABLE IF NOT EXISTS fake_events ("
    " received_at BIGINT NOT NULL,"
    " relay_url VARCHAR NOT NULL,"
    " claimed_id VARCHAR,"
    " claimed_pubkey VARCHAR,"
    " reason VARCHAR NOT NULL,"
    " raw VARCHAR)",

    "CREATE TABLE IF NOT EXISTS events ("
    " id VARCHAR PRIMARY KEY,"
    " kind BIGINT,"
    " created_at BIGINT,"
    " content VARCHAR,"
    " sig VARCHAR,"
    " raw VARCHAR,"
    " pubkey VARCHAR)",

    "CREATE TABLE IF NOT EXISTS profiles ("
    " pubkey VARCHAR PRIMARY KEY,"
    " created_at BIGINT,"
    " raw VARCHAR,"
    " name VARCHAR,"
    " display_name VARCHAR,"
    " about VARCHAR,"
    " picture VARCHAR,"
    " banner VARCHAR,"
    " nip05 VARCHAR,"
    " lud16 VARCHAR,"
    " lud06 VARCHAR,"
    " website VARCHAR)",

    # Flat, non-polymorphic tag store: one row per NIP-01 tag element. No
    # reference typing — `p`/`e`/`a` are read uniformly via `name` + the
    # primary value at `elem_idx=1` (see run_sql.md). `elem_idx=0` repeats
    # the tag name (NIP-01 array[0]); positions 1+ are its values. The
    # addressables coordinate, NIP-65 relay URLs, etc. are parsed at the
    # point of use, never baked into storage.
    "CREATE TABLE IF NOT EXISTS event_tags ("
    " event_id VARCHAR NOT NULL,"
    " tag_idx BIGINT NOT NULL,"
    " elem_idx BIGINT NOT NULL,"
    " name VARCHAR NOT NULL,"
    " value VARCHAR NOT NULL,"
    " PRIMARY KEY (event_id, tag_idx, elem_idx))",

    "CREATE TABLE IF NOT EXISTS event_seen_on ("
    " event_id VARCHAR NOT NULL,"
    " relay_url VARCHAR NOT NULL,"
    " PRIMARY KEY (event_id, relay_url))",

    # NIP-65 outbox/inbox model. One row per (pubkey, relay); the `read` /
    # `write` flags mirror the `r`-tag markers — a relay can be write-only,
    # read-only, or both (unmarked `r` tag sets both). Replaces the old
    # `pubkey_publishes_to` + `pubkey_reads_from` pair, which duplicated every
    # "both" relay across two identical tables.
    "CREATE TABLE IF NOT EXISTS pubkey_relays ("
    " pubkey VARCHAR NOT NULL,"
    " relay_url VARCHAR NOT NULL,"
    " read BOOLEAN NOT NULL DEFAULT FALSE,"
    " write BOOLEAN NOT NULL DEFAULT FALSE,"
    " PRIMARY KEY (pubkey, relay_url))",

    # Pubkeys we keep subscribed to. Two roles:
    #   'primary'   — explicitly tracked (via `subscribe_to_pubkey`); their
    #                 events feed back into this table as 'secondary' rows
    #                 derived from `p`-tag values.
    #   'secondary' — discovered through a primary's `p`-tags; we pull their
    #                 timeline + mentions exactly like a primary, but we do
    #                 NOT expand THEIR `p`-tags further (1-hop frontier).
    # `subscribe_to_pubkey` on an existing secondary promotes it to primary;
    # the live subs already running carry over unchanged. `last_scan_at IS NULL`
    # marks rows the auto-tracker hasn't run discovery on yet.
    "CREATE TABLE IF NOT EXISTS tracked_pubkeys ("
    " hex VARCHAR PRIMARY KEY,"
    " added_at BIGINT,"
    " last_scan_at BIGINT,"
    " limit_per_relay BIGINT,"
    " productive_relays VARCHAR[],"
    " role VARCHAR NOT NULL DEFAULT 'primary')",

    # Live-subscription coverage watermark. One row per (pubkey, relay,
    # direction): how far (unix seconds) we KNOW we've ingested everything
    # matching that live filter, because the relay sent its end-of-stored-
    # events signal and we kept listening. `direction` is 'out' (their own
    # events, `authors` filter) or 'in' (mentions, `#p` filter). This is the
    # `since` source on (re)subscribe — independent of when the last event
    # arrived, so a pubkey that went quiet doesn't drag `since` back to its
    # last post. Written by the periodic coverage flush + on shutdown.
    "CREATE TABLE IF NOT EXISTS live_coverage ("
    " pubkey VARCHAR NOT NULL,"
    " relay_url VARCHAR NOT NULL,"
    " direction VARCHAR NOT NULL,"
    " covered_through BIGINT NOT NULL,"
    " PRIMARY KEY (pubkey, relay_url, direction))",

    "CREATE INDEX IF NOT EXISTS events_author_idx     ON events(pubkey)",
    "CREATE INDEX IF NOT EXISTS events_kind_idx       ON events(kind)",
    "CREATE INDEX IF NOT EXISTS events_created_at_idx ON events(created_at)",
    "CREATE INDEX IF NOT EXISTS event_tags_event_idx  ON event_tags(event_id)",
    # Graph/value lookups (p→pubkey, e→event, title/status filters) all go
    # through (name, value) — one index serves every tag the same way.
    "CREATE INDEX IF NOT EXISTS event_tags_nv_idx     ON event_tags(name, value)",
)


# `addressables` is a VIEW over `event_tags`, not a base table — the NIP-33
# coordinate `kind:pubkey:d` is parsed at read time instead of being
# denormalized into columns at ingest. Created in `_ensure_schema` AFTER the
# legacy-table migration drops any pre-existing base table of the same name
# (a view can't shadow a table). `CREATE OR REPLACE` keeps it idempotent.
# The regex requires a numeric kind, then any (possibly empty) colon-free
# pubkey, then the rest as `d` (which may itself contain colons) — mirroring
# the old `split(":", 2)` parse exactly.
ADDRESSABLES_VIEW_DDL: str = (
    "CREATE OR REPLACE VIEW addressables AS "
    "SELECT DISTINCT "
    "  value, "
    "  CAST(regexp_extract(value, '^([0-9]+):([^:]*):(.*)$', 1) AS BIGINT) AS kind, "
    "  regexp_extract(value, '^([0-9]+):([^:]*):(.*)$', 2) AS pubkey, "
    "  regexp_extract(value, '^([0-9]+):([^:]*):(.*)$', 3) AS d "
    "FROM event_tags "
    "WHERE name = 'a' AND elem_idx = 1 "
    "  AND regexp_matches(value, '^[0-9]+:[^:]*:.*$')"
)


# Legacy column migrations for the `relays` table. ``CREATE TABLE IF NOT
# EXISTS`` above is a no-op on pre-existing DBs, so columns added after the
# initial release have to be reconciled here via ``ALTER TABLE … ADD COLUMN
# IF NOT EXISTS``. Each statement is independently idempotent.
#
# When a new column needs a non-trivial backfill (e.g. populate from another
# table's state), the conditional logic stays in
# ``IngestDaemonSQL._ensure_schema`` next to the legacy-detection probe —
# only the straight ``ALTER TABLE`` form lives here.
LEGACY_COLUMN_MIGRATIONS: tuple[str, ...] = (
    # Cooldown gate driver in `_read_active_relays_due`.
    "ALTER TABLE relays ADD COLUMN IF NOT EXISTS "
    "consecutive_failures BIGINT DEFAULT 0",

    # Discovery PHASE-2 win/lose counters; auto-disable threshold lives in
    # `DISCOVERY_REJECT_DISABLE_THRESHOLD` (ingest_sql.py).
    "ALTER TABLE relays ADD COLUMN IF NOT EXISTS "
    "discovery_picks BIGINT DEFAULT 0",
    "ALTER TABLE relays ADD COLUMN IF NOT EXISTS "
    "discovery_rejections BIGINT DEFAULT 0",

    # Per-relay counter of events that failed signature/id verification.
    # No backfill — pre-migration deployments had no way to detect fake
    # events (the verify path didn't exist), so historical 0 is honest.
    "ALTER TABLE relays ADD COLUMN IF NOT EXISTS "
    "invalid_events_count BIGINT DEFAULT 0",
)
