-- Migration: 004_create_einsaetze.sql
-- Feature: Einsatzprotokoll (Incident Management)
-- Depends on: 001_create_users_table.sql
-- Rollback instructions at bottom of file

-- ---------------------------------------------------------------------------
-- ENUM-LIKE CHECK CONSTRAINTS (as VARCHAR + CHECK for easy extension)
-- ---------------------------------------------------------------------------

-- ---------------------------------------------------------------------------
-- SEQUENCE SUPPORT TABLE for year-based Einsatz-Nr (YYYY-NNN)
-- One row per year; nextval equivalent done via UPDATE...RETURNING
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS einsatz_nr_sequence (
    year    INTEGER PRIMARY KEY,
    last_nr INTEGER NOT NULL DEFAULT 0
);

-- ---------------------------------------------------------------------------
-- FAHRZEUGE TABLE (vehicles — stub so junction table has a valid FK target)
-- A full Fahrzeuge feature is covered by 005_create_fahrzeuge.sql.
-- We do NOT create the table here; migration 005 owns it.
-- The FK in einsatz_fahrzeuge will resolve correctly when 005 runs first
-- in the standard sort order (004 runs before 005, so we use DEFERRABLE or
-- we simply add the FK as a separate ALTER TABLE at the end of this file,
-- after we know 005 may not have run yet).
--
-- Resolution strategy: einsatz_fahrzeuge.fahrzeug_id FK is defined as
-- DEFERRABLE INITIALLY DEFERRED so it is checked at transaction commit time
-- rather than at statement time, allowing this migration to run without
-- requiring fahrzeuge to exist yet.
-- ---------------------------------------------------------------------------

-- ---------------------------------------------------------------------------
-- MAIN EINSAETZE TABLE
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS einsaetze (
    -- Identity
    id              UUID        PRIMARY KEY DEFAULT uuid_generate_v4(),
    einsatz_nr      VARCHAR(12) NOT NULL,               -- Format: 2026-001

    -- Core timestamps (all TIMESTAMPTZ — never null for alarm_time)
    alarm_time      TIMESTAMPTZ NOT NULL,
    ausrueck_time   TIMESTAMPTZ,
    ankunft_time    TIMESTAMPTZ,
    einrueck_time   TIMESTAMPTZ,

    -- Classification
    einsatz_art     VARCHAR(30) NOT NULL
        CONSTRAINT chk_einsatz_art CHECK (einsatz_art IN (
            'Brand',
            'THL',
            'ABC',
            'BMA',
            'Hilfeleistung',
            'Fehlalarm',
            'Brandsicherheitswache'
        )),
    einsatz_stichwort VARCHAR(30),                     -- B1/B2/B3/B4, THL 1/2/3, etc.

    -- Location
    strasse         VARCHAR(150),
    hausnummer      VARCHAR(20),
    ort             VARCHAR(100),
    koordinaten     POINT,                              -- (longitude, latitude)

    -- Narrative
    bericht_kurz    VARCHAR(255),                       -- short description, visible to all
    bericht_text    TEXT,                               -- full narrative, restricted to Kommandant+

    -- References
    einsatzleiter_id UUID
        CONSTRAINT fk_einsaetze_einsatzleiter REFERENCES users(id) ON DELETE SET NULL,

    -- Operational metadata
    alarmierung_art VARCHAR(30) NOT NULL DEFAULT 'ILS'
        CONSTRAINT chk_alarmierung_art CHECK (alarmierung_art IN (
            'ILS',
            'DME',
            'Telefon',
            'Vor_Ort',
            'Sonstiges'
        )),
    status          VARCHAR(20) NOT NULL DEFAULT 'aktiv'
        CONSTRAINT chk_einsatz_status CHECK (status IN (
            'aktiv',
            'abgeschlossen',
            'archiviert'
        )),

    -- Audit
    created_by      UUID
        CONSTRAINT fk_einsaetze_created_by REFERENCES users(id) ON DELETE SET NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    -- Uniqueness: einsatz_nr is globally unique (year+seq guarantees it)
    CONSTRAINT uq_einsatz_nr UNIQUE (einsatz_nr)
);

-- Performance indexes
CREATE INDEX IF NOT EXISTS idx_einsaetze_alarm_time      ON einsaetze(alarm_time DESC);
CREATE INDEX IF NOT EXISTS idx_einsaetze_einsatz_art     ON einsaetze(einsatz_art);
CREATE INDEX IF NOT EXISTS idx_einsaetze_status          ON einsaetze(status);
CREATE INDEX IF NOT EXISTS idx_einsaetze_einsatzleiter   ON einsaetze(einsatzleiter_id);
-- Note: EXTRACT(YEAR FROM timestamptz) is STABLE (timezone-dependent), not IMMUTABLE,
-- so it cannot be used in expression indexes. Use alarm_time range scans for year filtering.
CREATE INDEX IF NOT EXISTS idx_einsaetze_alarm_art       ON einsaetze(einsatz_art, alarm_time DESC);

-- Auto-update updated_at
CREATE TRIGGER update_einsaetze_updated_at
    BEFORE UPDATE ON einsaetze
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ---------------------------------------------------------------------------
-- JUNCTION TABLE: einsatz_fahrzeuge
-- NOTE: fahrzeug_id FK references fahrzeuge which is created in 005.
-- We define the FK as DEFERRABLE INITIALLY DEFERRED so the constraint is
-- validated at transaction commit, not statement time. This means the
-- migration can run before 005 as long as both run in the same session.
-- In production where 005 was already run, the FK resolves immediately.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS einsatz_fahrzeuge (
    einsatz_id      UUID        NOT NULL
        CONSTRAINT fk_ef_einsatz REFERENCES einsaetze(id) ON DELETE CASCADE,
    fahrzeug_id     UUID        NOT NULL,
    -- FK added separately below via ALTER TABLE to handle cross-migration dependency

    -- Vehicle-level timestamps (may differ from main einsatz times)
    ausrueck_time   TIMESTAMPTZ,
    einrueck_time   TIMESTAMPTZ,

    assigned_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT pk_einsatz_fahrzeuge PRIMARY KEY (einsatz_id, fahrzeug_id)
);

-- Add the FK to fahrzeuge only if the fahrzeuge table already exists
-- (handles the case where 004 runs after 005 in a fresh deployment).
-- If fahrzeuge does not exist yet, the FK will be added by migration 005
-- via an ALTER TABLE at the end of that migration.
DO $$
BEGIN
    IF EXISTS (
        SELECT 1 FROM information_schema.tables
        WHERE table_schema = 'public' AND table_name = 'fahrzeuge'
    ) THEN
        IF NOT EXISTS (
            SELECT 1 FROM information_schema.table_constraints
            WHERE table_name = 'einsatz_fahrzeuge'
              AND constraint_name = 'fk_ef_fahrzeug'
        ) THEN
            ALTER TABLE einsatz_fahrzeuge
                ADD CONSTRAINT fk_ef_fahrzeug
                FOREIGN KEY (fahrzeug_id) REFERENCES fahrzeuge(id) ON DELETE CASCADE;
        END IF;
    END IF;
END $$;

CREATE INDEX IF NOT EXISTS idx_ef_fahrzeug_id ON einsatz_fahrzeuge(fahrzeug_id);

-- ---------------------------------------------------------------------------
-- JUNCTION TABLE: einsatz_personal
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS einsatz_personal (
    einsatz_id      UUID        NOT NULL
        CONSTRAINT fk_ep_einsatz REFERENCES einsaetze(id) ON DELETE CASCADE,
    user_id         UUID        NOT NULL
        CONSTRAINT fk_ep_user REFERENCES users(id) ON DELETE CASCADE,

    funktion        VARCHAR(50) NOT NULL DEFAULT 'Mannschaft'
        CONSTRAINT chk_ep_funktion CHECK (funktion IN (
            'Einsatzleiter',
            'Gruppenführer',
            'Maschinist',
            'Atemschutz',
            'Sicherheitstrupp',
            'Melder',
            'Wassertrupp',
            'Angriffstrupp',
            'Mannschaft',
            'Sonstiges'
        )),

    -- Personal-level timestamps
    alarm_time      TIMESTAMPTZ,
    ankunft_time    TIMESTAMPTZ,

    assigned_at     TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    CONSTRAINT pk_einsatz_personal PRIMARY KEY (einsatz_id, user_id)
);

CREATE INDEX IF NOT EXISTS idx_ep_user_id    ON einsatz_personal(user_id);
CREATE INDEX IF NOT EXISTS idx_ep_funktion   ON einsatz_personal(funktion);

-- ---------------------------------------------------------------------------
-- FUNCTION: generate_einsatz_nr(alarm_time)
-- Atomically generates the next Einsatz-Nr for a given year.
-- Uses an advisory lock to serialise concurrent inserts for the same year.
-- Returns format: '2026-001'
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION generate_einsatz_nr(p_alarm_time TIMESTAMPTZ)
RETURNS VARCHAR AS $$
DECLARE
    v_year  INTEGER;
    v_nr    INTEGER;
BEGIN
    v_year := EXTRACT(YEAR FROM p_alarm_time)::INTEGER;

    -- Upsert the sequence row, then atomically increment and return
    INSERT INTO einsatz_nr_sequence (year, last_nr)
        VALUES (v_year, 1)
        ON CONFLICT (year) DO UPDATE
            SET last_nr = einsatz_nr_sequence.last_nr + 1
        RETURNING last_nr INTO v_nr;

    RETURN v_year::TEXT || '-' || LPAD(v_nr::TEXT, 3, '0');
END;
$$ LANGUAGE plpgsql;

-- ---------------------------------------------------------------------------
-- MATERIALIZED VIEW: einsatz_statistik
-- Pre-aggregated stats used by the dashboard and annual KBI reports.
-- Refresh manually after inserts/updates via: REFRESH MATERIALIZED VIEW einsatz_statistik;
-- ---------------------------------------------------------------------------
CREATE MATERIALIZED VIEW IF NOT EXISTS einsatz_statistik AS
SELECT
    EXTRACT(YEAR FROM alarm_time)::INTEGER   AS jahr,
    EXTRACT(MONTH FROM alarm_time)::INTEGER  AS monat,
    einsatz_art,
    COUNT(*)                                 AS anzahl,
    -- Hilfsfrist: median minutes from alarm to arrival (only where ankunft_time is set)
    ROUND(
        AVG(
            EXTRACT(EPOCH FROM (ankunft_time - alarm_time)) / 60.0
        ) FILTER (WHERE ankunft_time IS NOT NULL)
    )::INTEGER                               AS avg_hilfsfrist_min,
    -- Total duration (alarm to einrueck)
    ROUND(
        AVG(
            EXTRACT(EPOCH FROM (einrueck_time - alarm_time)) / 60.0
        ) FILTER (WHERE einrueck_time IS NOT NULL)
    )::INTEGER                               AS avg_dauer_min,
    COUNT(*) FILTER (WHERE status = 'abgeschlossen')  AS anzahl_abgeschlossen,
    COUNT(*) FILTER (WHERE status = 'aktiv')          AS anzahl_aktiv
FROM einsaetze
WHERE status != 'archiviert'
GROUP BY
    EXTRACT(YEAR FROM alarm_time),
    EXTRACT(MONTH FROM alarm_time),
    einsatz_art
WITH DATA;

CREATE UNIQUE INDEX IF NOT EXISTS idx_einsatz_statistik_pk
    ON einsatz_statistik(jahr, monat, einsatz_art);

CREATE INDEX IF NOT EXISTS idx_einsatz_statistik_jahr
    ON einsatz_statistik(jahr);

-- ---------------------------------------------------------------------------
-- ROLLBACK INSTRUCTIONS (run in reverse order to undo):
--
-- DROP MATERIALIZED VIEW IF EXISTS einsatz_statistik;
-- DROP FUNCTION IF EXISTS generate_einsatz_nr(TIMESTAMPTZ);
-- DROP TABLE IF EXISTS einsatz_personal;
-- DROP TABLE IF EXISTS einsatz_fahrzeuge;
-- DROP TABLE IF EXISTS einsaetze;
-- DROP TABLE IF EXISTS fahrzeuge;
-- DROP TABLE IF EXISTS einsatz_nr_sequence;
-- ---------------------------------------------------------------------------
