-- LEAF Portal — PostgreSQL schema
-- Applied automatically on first run (via /setup) and by the CI init script.
-- All statements are idempotent (CREATE TABLE IF NOT EXISTS / CREATE OR REPLACE).

CREATE TABLE IF NOT EXISTS organization (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name       TEXT NOT NULL UNIQUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS department (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID NOT NULL REFERENCES organization(id) ON DELETE RESTRICT,
    name            TEXT NOT NULL,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (organization_id, name)
);
CREATE INDEX IF NOT EXISTS idx_department_organization ON department (organization_id);

CREATE TABLE IF NOT EXISTS user_account (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email         TEXT NOT NULL UNIQUE,
    name          TEXT NOT NULL,
    password_hash TEXT,
    is_superadmin BOOLEAN NOT NULL DEFAULT false,
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS user_organization (
    user_id         UUID NOT NULL REFERENCES user_account(id) ON DELETE CASCADE,
    organization_id UUID NOT NULL REFERENCES organization(id) ON DELETE RESTRICT,
    role            TEXT NOT NULL DEFAULT 'member',
    granted_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (user_id, organization_id)
);

CREATE TABLE IF NOT EXISTS user_department (
    user_id       UUID NOT NULL REFERENCES user_account(id) ON DELETE CASCADE,
    department_id UUID NOT NULL REFERENCES department(id) ON DELETE RESTRICT,
    role          TEXT NOT NULL DEFAULT 'member',
    granted_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (user_id, department_id)
);

CREATE TABLE IF NOT EXISTS password_reset_token (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id    UUID NOT NULL REFERENCES user_account(id) ON DELETE CASCADE,
    token      TEXT NOT NULL UNIQUE,
    expires_at TIMESTAMPTZ NOT NULL,
    used_at    TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_prt_token ON password_reset_token (token);

CREATE TABLE IF NOT EXISTS api_token (
    id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id    UUID NOT NULL REFERENCES user_account(id) ON DELETE CASCADE,
    name       TEXT NOT NULL,
    token      TEXT NOT NULL UNIQUE,
    expires_at TIMESTAMPTZ,
    revoked_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_api_token_token ON api_token (token);
CREATE INDEX IF NOT EXISTS idx_api_token_user  ON api_token (user_id);

CREATE TABLE IF NOT EXISTS management (
    id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id UUID NOT NULL REFERENCES organization(id) ON DELETE RESTRICT,
    department_id   UUID REFERENCES department(id) ON DELETE RESTRICT,
    entity          TEXT,
    time_start      TIMESTAMPTZ,
    time_end        TIMESTAMPTZ,
    name            TEXT NOT NULL,
    description     TEXT,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    CONSTRAINT management_entity_requires_department
        CHECK (entity IS NULL OR department_id IS NOT NULL)
);
CREATE INDEX IF NOT EXISTS idx_management_organization ON management (organization_id);
CREATE INDEX IF NOT EXISTS idx_management_department   ON management (department_id);

CREATE TABLE IF NOT EXISTS user_management (
    user_id       UUID NOT NULL REFERENCES user_account(id) ON DELETE CASCADE,
    management_id UUID NOT NULL REFERENCES management(id)   ON DELETE CASCADE,
    role          TEXT NOT NULL DEFAULT 'viewer',
    granted_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (user_id, management_id)
);
CREATE INDEX IF NOT EXISTS idx_user_management_proj ON user_management (management_id);
CREATE INDEX IF NOT EXISTS idx_user_management_user ON user_management (user_id);

CREATE TABLE IF NOT EXISTS mapper_language (
    key   TEXT NOT NULL,
    lang  TEXT NOT NULL,
    value TEXT NOT NULL,
    PRIMARY KEY (key, lang)
);

CREATE TABLE IF NOT EXISTS mapper_semantics (
    key   TEXT PRIMARY KEY,
    value TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS sensor_data (
    time            TIMESTAMPTZ      NOT NULL,
    entity          TEXT             NOT NULL,
    metric          TEXT             NOT NULL,
    value           DOUBLE PRECISION NOT NULL,
    tags            JSONB,
    department_id   UUID             NOT NULL,
    organization_id UUID             NOT NULL,
    UNIQUE (time, entity, metric, department_id, organization_id)
);
CREATE INDEX IF NOT EXISTS idx_sensor_data_department   ON sensor_data (department_id, time DESC);
CREATE INDEX IF NOT EXISTS idx_sensor_data_organization ON sensor_data (organization_id, time DESC);

CREATE TABLE IF NOT EXISTS alarm_rule (
    id                 UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name               TEXT NOT NULL,
    department_id      UUID NOT NULL REFERENCES department(id)   ON DELETE CASCADE,
    organization_id    UUID NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
    entity             TEXT NOT NULL,
    metric             TEXT NOT NULL,
    operator           TEXT NOT NULL CHECK (operator IN ('<', '>', '<=', '>=', '=')),
    threshold          DOUBLE PRECISION NOT NULL,
    check_interval     INT NOT NULL DEFAULT 300,
    enabled            BOOLEAN NOT NULL DEFAULT TRUE,
    created_at         TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by         UUID REFERENCES user_account(id) ON DELETE SET NULL,
    last_checked_at    TIMESTAMPTZ,
    last_checked_value DOUBLE PRECISION
);
CREATE INDEX IF NOT EXISTS idx_alarm_rule_department ON alarm_rule (department_id);

CREATE TABLE IF NOT EXISTS alarm_recipient (
    rule_id UUID NOT NULL REFERENCES alarm_rule(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES user_account(id) ON DELETE CASCADE,
    PRIMARY KEY (rule_id, user_id)
);

CREATE TABLE IF NOT EXISTS alarm_event (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    rule_id     UUID NOT NULL REFERENCES alarm_rule(id) ON DELETE CASCADE,
    status      TEXT NOT NULL CHECK (status IN ('triggered', 'resolved')),
    value       DOUBLE PRECISION,
    occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_alarm_event_rule ON alarm_event (rule_id, occurred_at DESC);

CREATE TABLE IF NOT EXISTS setting (
    namespace  TEXT NOT NULL DEFAULT 'general',
    key        TEXT NOT NULL,
    value      TEXT,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (namespace, key)
);

CREATE OR REPLACE FUNCTION sensor_data_for_department_per_uuid(
    p_user_email    TEXT,
    p_department_id UUID
) RETURNS TABLE (
    "time"          TIMESTAMPTZ,
    entity          TEXT,
    metric          TEXT,
    value           DOUBLE PRECISION,
    tags            JSONB,
    department_id   UUID,
    organization_id UUID
) LANGUAGE sql AS $$
    SELECT sd."time", sd.entity, sd.metric, sd.value, sd.tags,
           sd.department_id, sd.organization_id
    FROM   sensor_data sd
    WHERE  sd.department_id = p_department_id
    AND    EXISTS (
        SELECT 1
        FROM   user_management um
        JOIN   management      m  ON m.id  = um.management_id
        JOIN   user_account    ua ON ua.id = um.user_id
        WHERE  ua.email                 = p_user_email
        AND    m.organization_id        = sd.organization_id
        AND    (m.department_id IS NULL OR m.department_id = sd.department_id)
        AND    (m.entity        IS NULL OR m.entity        = sd.entity)
        AND    (m.time_start    IS NULL OR sd."time" >= m.time_start)
        AND    (m.time_end      IS NULL OR sd."time" <  m.time_end)
    );
$$;

CREATE OR REPLACE FUNCTION sensor_data_recent(
    p_user_email TEXT,
    p_limit      INT
) RETURNS TABLE (
    "time"          TIMESTAMPTZ,
    entity          TEXT,
    metric          TEXT,
    value           DOUBLE PRECISION,
    tags            JSONB,
    department_id   UUID,
    organization_id UUID
) LANGUAGE sql AS $$
    SELECT sd."time", sd.entity, sd.metric, sd.value, sd.tags,
           sd.department_id, sd.organization_id
    FROM   sensor_data sd
    WHERE  EXISTS (
        SELECT 1
        FROM   user_management um
        JOIN   management      m  ON m.id  = um.management_id
        JOIN   user_account    ua ON ua.id = um.user_id
        WHERE  ua.email                 = p_user_email
        AND    m.organization_id        = sd.organization_id
        AND    (m.department_id IS NULL OR m.department_id = sd.department_id)
        AND    (m.entity        IS NULL OR m.entity        = sd.entity)
        AND    (m.time_start    IS NULL OR sd."time" >= m.time_start)
        AND    (m.time_end      IS NULL OR sd."time" <  m.time_end)
    )
    ORDER  BY sd."time" DESC
    LIMIT  p_limit;
$$;

CREATE OR REPLACE FUNCTION management_for_token(
    p_token TEXT
) RETURNS TABLE (
    id                UUID,
    name              TEXT,
    organization_name TEXT,
    department_name   TEXT,
    entity            TEXT,
    time_start        TIMESTAMPTZ,
    time_end          TIMESTAMPTZ
) LANGUAGE sql AS $$
    SELECT DISTINCT
        m.id,
        m.name,
        o.name  AS organization_name,
        d.name  AS department_name,
        m.entity,
        m.time_start,
        m.time_end
    FROM   management      m
    JOIN   organization    o  ON o.id  = m.organization_id
    LEFT JOIN department   d  ON d.id  = m.department_id
    JOIN   user_management um ON um.management_id = m.id
    JOIN   user_account    ua ON ua.id = um.user_id
    JOIN   api_token       at ON at.user_id = ua.id
    WHERE  at.token = p_token
      AND  at.revoked_at IS NULL
      AND  (at.expires_at IS NULL OR at.expires_at > now());
$$;

CREATE OR REPLACE FUNCTION sensor_data_recent_by_token(
    p_token TEXT,
    p_limit INT
) RETURNS TABLE (
    "time"          TIMESTAMPTZ,
    entity          TEXT,
    metric          TEXT,
    value           DOUBLE PRECISION,
    tags            JSONB,
    department_id   UUID,
    organization_id UUID
) LANGUAGE sql AS $$
    SELECT sd."time", sd.entity, sd.metric, sd.value, sd.tags,
           sd.department_id, sd.organization_id
    FROM   sensor_data sd
    JOIN  (
        SELECT DISTINCT m.department_id
        FROM   management      m
        JOIN   user_management um ON um.management_id = m.id
        JOIN   user_account    ua ON ua.id = um.user_id
        JOIN   api_token       at ON at.user_id = ua.id
        WHERE  at.token = p_token
          AND  at.revoked_at IS NULL
          AND  (at.expires_at IS NULL OR at.expires_at > now())
    ) acc ON sd.department_id = acc.department_id
    ORDER  BY sd."time" DESC
    LIMIT  p_limit;
$$;

CREATE OR REPLACE FUNCTION sensor_data_for_department_by_token(
    p_token        TEXT,
    p_organization TEXT,
    p_department   TEXT
) RETURNS TABLE (
    "time"          TIMESTAMPTZ,
    entity          TEXT,
    metric          TEXT,
    value           DOUBLE PRECISION,
    tags            JSONB,
    department_id   UUID,
    organization_id UUID
) LANGUAGE sql AS $$
    SELECT sd."time", sd.entity, sd.metric, sd.value, sd.tags,
           sd.department_id, sd.organization_id
    FROM   sensor_data sd
    JOIN  (
        SELECT DISTINCT m.department_id
        FROM   management      m
        JOIN   organization    o  ON o.id  = m.organization_id
        JOIN   department      d  ON d.id  = m.department_id
        JOIN   user_management um ON um.management_id = m.id
        JOIN   user_account    ua ON ua.id = um.user_id
        JOIN   api_token       at ON at.user_id = ua.id
        WHERE  at.token = p_token
          AND  at.revoked_at IS NULL
          AND  (at.expires_at IS NULL OR at.expires_at > now())
          AND  o.name = p_organization
          AND  d.name = p_department
    ) acc ON sd.department_id = acc.department_id
    ORDER  BY sd."time" DESC;
$$;
