CREATE TABLE IF NOT EXISTS schema_migrations (
    version INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    applied_at TEXT NOT NULL,
    checksum TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS jobs (
    id INTEGER PRIMARY KEY,
    job_key TEXT NOT NULL UNIQUE CHECK (job_key = lower(job_key) AND job_key NOT GLOB '*[^a-z0-9.]*'
        AND length(job_key) - length(replace(job_key, '.', '')) >= 2),
    name TEXT NOT NULL,
    description TEXT,
    source_type TEXT NOT NULL,
    job_type TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
    expected_interval_seconds INTEGER CHECK (expected_interval_seconds IS NULL OR expected_interval_seconds > 0),
    timeout_seconds INTEGER CHECK (timeout_seconds IS NULL OR timeout_seconds > 0),
    stale_after_seconds INTEGER CHECK (stale_after_seconds IS NULL OR stale_after_seconds > 0),
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    metadata_json TEXT
);
CREATE INDEX IF NOT EXISTS idx_jobs_enabled ON jobs(enabled);

CREATE TABLE IF NOT EXISTS job_instances (
    id INTEGER PRIMARY KEY,
    job_id INTEGER NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
    instance_key TEXT NOT NULL UNIQUE,
    host TEXT NOT NULL,
    instance_name TEXT,
    runtime_type TEXT NOT NULL,
    service_name TEXT,
    container_name TEXT,
    process_name TEXT,
    source_reference TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
    first_seen_at TEXT NOT NULL,
    last_seen_at TEXT NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    metadata_json TEXT
);
CREATE INDEX IF NOT EXISTS idx_job_instances_job_id ON job_instances(job_id);
CREATE INDEX IF NOT EXISTS idx_job_instances_host ON job_instances(host);
CREATE INDEX IF NOT EXISTS idx_job_instances_runtime_type ON job_instances(runtime_type);

CREATE TABLE IF NOT EXISTS job_runs (
    id INTEGER PRIMARY KEY,
    instance_id INTEGER NOT NULL REFERENCES job_instances(id) ON DELETE CASCADE,
    external_run_id TEXT,
    started_at TEXT,
    finished_at TEXT,
    operational_state TEXT NOT NULL CHECK (operational_state IN ('scheduled','running','waiting','paused','stopped','completed','unknown')),
    health_status TEXT NOT NULL CHECK (health_status IN ('healthy','degraded','failed','stale','unknown')),
    exit_code INTEGER,
    runtime_seconds REAL CHECK (runtime_seconds IS NULL OR runtime_seconds >= 0),
    records_processed INTEGER,
    error_message TEXT,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    metadata_json TEXT,
    UNIQUE(instance_id, external_run_id)
);
CREATE INDEX IF NOT EXISTS idx_job_runs_instance_id ON job_runs(instance_id);

CREATE TABLE IF NOT EXISTS collector_runs (
    id INTEGER PRIMARY KEY,
    collector_name TEXT NOT NULL,
    collector_version TEXT NOT NULL,
    started_at TEXT NOT NULL,
    finished_at TEXT,
    success INTEGER NOT NULL CHECK (success IN (0, 1)),
    error_type TEXT,
    error_message TEXT,
    jobs_seen INTEGER NOT NULL DEFAULT 0,
    instances_seen INTEGER NOT NULL DEFAULT 0,
    observations_created INTEGER NOT NULL DEFAULT 0,
    duration_ms INTEGER,
    metadata_json TEXT
);
CREATE INDEX IF NOT EXISTS idx_collector_runs_started_at ON collector_runs(started_at);

CREATE TABLE IF NOT EXISTS job_observations (
    id INTEGER PRIMARY KEY,
    instance_id INTEGER NOT NULL REFERENCES job_instances(id) ON DELETE CASCADE,
    run_id INTEGER REFERENCES job_runs(id) ON DELETE SET NULL,
    collector_run_id INTEGER REFERENCES collector_runs(id) ON DELETE SET NULL,
    observed_at TEXT NOT NULL,
    operational_state TEXT NOT NULL CHECK (operational_state IN ('scheduled','running','waiting','paused','stopped','completed','unknown')),
    health_status TEXT NOT NULL CHECK (health_status IN ('healthy','degraded','failed','stale','unknown')),
    observation_status TEXT NOT NULL CHECK (observation_status IN ('fresh','stale','unavailable')),
    severity TEXT NOT NULL CHECK (severity IN ('info','warning','critical')),
    status_reason TEXT NOT NULL,
    last_activity_at TEXT,
    expected_next_run_at TEXT,
    heartbeat_at TEXT,
    exit_code INTEGER,
    evidence_json TEXT NOT NULL,
    raw_payload_json TEXT,
    created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_job_observations_instance_at ON job_observations(instance_id, observed_at);

CREATE TABLE IF NOT EXISTS job_status_current (
    instance_id INTEGER PRIMARY KEY REFERENCES job_instances(id) ON DELETE CASCADE,
    run_id INTEGER REFERENCES job_runs(id) ON DELETE SET NULL,
    operational_state TEXT NOT NULL CHECK (operational_state IN ('scheduled','running','waiting','paused','stopped','completed','unknown')),
    health_status TEXT NOT NULL CHECK (health_status IN ('healthy','degraded','failed','stale','unknown')),
    observation_status TEXT NOT NULL CHECK (observation_status IN ('fresh','stale','unavailable')),
    severity TEXT NOT NULL CHECK (severity IN ('info','warning','critical')),
    status_reason TEXT NOT NULL,
    last_observed_at TEXT NOT NULL,
    last_activity_at TEXT,
    last_started_at TEXT,
    last_finished_at TEXT,
    last_success_at TEXT,
    last_failure_at TEXT,
    expected_next_run_at TEXT,
    heartbeat_at TEXT,
    exit_code INTEGER,
    last_error TEXT,
    collector_name TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    evidence_json TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_current_health ON job_status_current(health_status);
CREATE INDEX IF NOT EXISTS idx_current_operational ON job_status_current(operational_state);
CREATE INDEX IF NOT EXISTS idx_current_severity ON job_status_current(severity);
CREATE INDEX IF NOT EXISTS idx_current_activity ON job_status_current(last_activity_at);

CREATE TABLE IF NOT EXISTS status_events (
    id INTEGER PRIMARY KEY,
    instance_id INTEGER NOT NULL REFERENCES job_instances(id) ON DELETE CASCADE,
    run_id INTEGER REFERENCES job_runs(id) ON DELETE SET NULL,
    event_type TEXT NOT NULL,
    previous_operational_state TEXT,
    new_operational_state TEXT,
    previous_health_status TEXT,
    new_health_status TEXT,
    previous_observation_status TEXT,
    new_observation_status TEXT,
    previous_severity TEXT,
    new_severity TEXT,
    reason TEXT NOT NULL,
    occurred_at TEXT NOT NULL,
    observation_id INTEGER REFERENCES job_observations(id) ON DELETE SET NULL,
    metadata_json TEXT
);
CREATE INDEX IF NOT EXISTS idx_status_events_instance_at ON status_events(instance_id, occurred_at);

CREATE TABLE IF NOT EXISTS watcher_events (
    id INTEGER PRIMARY KEY,
    event_type TEXT NOT NULL,
    severity TEXT NOT NULL CHECK (severity IN ('info','warning','critical')),
    message TEXT NOT NULL,
    created_at TEXT NOT NULL,
    component TEXT NOT NULL,
    metadata_json TEXT
);
CREATE INDEX IF NOT EXISTS idx_watcher_events_created_at ON watcher_events(created_at);
