CREATE TABLE organizations (
    id uuid PRIMARY KEY,
    slug text NOT NULL UNIQUE,
    name text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE users (
    id uuid PRIMARY KEY,
    identity_issuer text NOT NULL,
    identity_subject text NOT NULL,
    display_name text NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    CHECK (length(identity_issuer) > 0),
    CHECK (length(identity_subject) > 0),
    UNIQUE (identity_issuer, identity_subject)
);

CREATE TABLE organization_memberships (
    organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE,
    user_id uuid NOT NULL REFERENCES users (id) ON DELETE RESTRICT,
    role text NOT NULL CHECK (role IN ('owner', 'administrator', 'member', 'viewer')),
    created_at timestamptz NOT NULL DEFAULT now(),
    removed_at timestamptz,
    PRIMARY KEY (organization_id, user_id)
);

CREATE INDEX organization_memberships_user_idx
    ON organization_memberships (user_id);

CREATE TABLE projects (
    organization_id uuid NOT NULL REFERENCES organizations (id) ON DELETE CASCADE,
    id uuid NOT NULL,
    slug text NOT NULL,
    name text NOT NULL,
    remediation_mode text NOT NULL DEFAULT 'report_before_change'
        CHECK (remediation_mode IN ('report_before_change', 'auto_change_then_report')),
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    UNIQUE (organization_id, slug)
);

CREATE TABLE ingestion_sources (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    name text NOT NULL,
    credential_reference text NOT NULL,
    status text NOT NULL DEFAULT 'active'
        CHECK (status IN ('active', 'disabled')),
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    CHECK (length(credential_reference) > 0),
    UNIQUE (organization_id, id, project_id),
    UNIQUE (organization_id, project_id, name),
    UNIQUE (organization_id, credential_reference),
    FOREIGN KEY (organization_id, project_id)
        REFERENCES projects (organization_id, id) ON DELETE CASCADE
);

CREATE INDEX ingestion_sources_project_idx
    ON ingestion_sources (organization_id, project_id);

CREATE TABLE repositories (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    provider text NOT NULL,
    clone_url text NOT NULL,
    default_branch text NOT NULL,
    credential_reference text,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    UNIQUE (organization_id, id, project_id),
    FOREIGN KEY (organization_id, project_id)
        REFERENCES projects (organization_id, id) ON DELETE CASCADE
);

CREATE INDEX repositories_project_idx
    ON repositories (organization_id, project_id);

CREATE TABLE runtime_environments (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    name text NOT NULL,
    connection_type text NOT NULL CHECK (connection_type IN ('ssh', 'collector')),
    endpoint text,
    credential_reference text,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    UNIQUE (organization_id, id, project_id),
    UNIQUE (organization_id, project_id, name),
    FOREIGN KEY (organization_id, project_id)
        REFERENCES projects (organization_id, id) ON DELETE CASCADE
);

CREATE INDEX runtime_environments_project_idx
    ON runtime_environments (organization_id, project_id);

CREATE TABLE analysis_target_rules (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    ingestion_source_id uuid,
    priority integer NOT NULL CHECK (priority >= 0),
    source_system text,
    source_service text,
    source_environment text,
    source_component text,
    repository_id uuid NOT NULL,
    enabled boolean NOT NULL DEFAULT true,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    UNIQUE (organization_id, id, project_id),
    UNIQUE (organization_id, project_id, priority),
    FOREIGN KEY (organization_id, ingestion_source_id, project_id)
        REFERENCES ingestion_sources (organization_id, id, project_id)
        ON DELETE CASCADE,
    FOREIGN KEY (organization_id, repository_id, project_id)
        REFERENCES repositories (organization_id, id, project_id)
        ON DELETE RESTRICT
);

CREATE INDEX analysis_target_rules_match_idx
    ON analysis_target_rules (
        organization_id,
        project_id,
        enabled,
        priority
    );

CREATE TABLE analysis_target_rule_runtime_environments (
    organization_id uuid NOT NULL,
    analysis_target_rule_id uuid NOT NULL,
    project_id uuid NOT NULL,
    runtime_environment_id uuid NOT NULL,
    PRIMARY KEY (
        organization_id,
        analysis_target_rule_id,
        runtime_environment_id
    ),
    FOREIGN KEY (organization_id, analysis_target_rule_id, project_id)
        REFERENCES analysis_target_rules (organization_id, id, project_id)
        ON DELETE CASCADE,
    FOREIGN KEY (organization_id, runtime_environment_id, project_id)
        REFERENCES runtime_environments (organization_id, id, project_id)
        ON DELETE RESTRICT
);

CREATE INDEX analysis_target_rule_runtime_environment_idx
    ON analysis_target_rule_runtime_environments (
        organization_id,
        runtime_environment_id,
        project_id
    );

CREATE TABLE error_groups (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    fingerprint text NOT NULL,
    error_type text NOT NULL,
    latest_message text NOT NULL,
    occurrence_count bigint NOT NULL DEFAULT 0 CHECK (occurrence_count >= 0),
    first_seen_at timestamptz NOT NULL,
    last_seen_at timestamptz NOT NULL,
    status text NOT NULL DEFAULT 'open'
        CHECK (status IN ('open', 'analyzing', 'actionable', 'resolved', 'ignored')),
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    CHECK (first_seen_at <= last_seen_at),
    UNIQUE (organization_id, id, project_id),
    UNIQUE (organization_id, project_id, fingerprint),
    FOREIGN KEY (organization_id, project_id)
        REFERENCES projects (organization_id, id) ON DELETE CASCADE
);

CREATE INDEX error_groups_project_status_idx
    ON error_groups (organization_id, project_id, status, last_seen_at DESC);

CREATE TABLE error_occurrences (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    ingestion_source_id uuid NOT NULL,
    error_group_id uuid NOT NULL,
    source_event_id text NOT NULL,
    occurred_at timestamptz NOT NULL,
    payload jsonb NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    CHECK (length(source_event_id) > 0),
    UNIQUE (organization_id, ingestion_source_id, source_event_id),
    FOREIGN KEY (organization_id, ingestion_source_id, project_id)
        REFERENCES ingestion_sources (organization_id, id, project_id)
        ON DELETE RESTRICT,
    FOREIGN KEY (organization_id, error_group_id, project_id)
        REFERENCES error_groups (organization_id, id, project_id) ON DELETE CASCADE
);

CREATE INDEX error_occurrences_group_idx
    ON error_occurrences (organization_id, error_group_id, occurred_at DESC);

CREATE INDEX error_occurrences_ingestion_source_idx
    ON error_occurrences (
        organization_id,
        ingestion_source_id,
        occurred_at DESC
    );

CREATE TABLE remediation_runs (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    error_group_id uuid NOT NULL,
    repository_id uuid NOT NULL,
    base_revision text NOT NULL,
    mode text NOT NULL CHECK (mode IN ('report_before_change', 'auto_change_then_report')),
    status text NOT NULL
        CHECK (status IN ('queued', 'analyzing', 'awaiting_approval', 'changing', 'validating', 'completed', 'failed', 'cancelled')),
    fixability text NOT NULL DEFAULT 'undetermined'
        CHECK (fixability IN ('fixable', 'not_fixable', 'undetermined')),
    proposal_digest text,
    report jsonb,
    failure jsonb,
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    completed_at timestamptz,
    PRIMARY KEY (organization_id, id),
    UNIQUE (organization_id, id, project_id),
    UNIQUE (organization_id, id, project_id, repository_id),
    UNIQUE (organization_id, id, project_id, proposal_digest),
    CHECK (proposal_digest IS NULL OR length(proposal_digest) > 0),
    CHECK (
        failure IS NULL OR
        failure ->> 'schema_version' = 'ariadne.execution-failure.v1'
    ),
    CHECK (
        (status IN ('completed', 'failed', 'cancelled')) =
        (completed_at IS NOT NULL)
    ),
    FOREIGN KEY (organization_id, error_group_id, project_id)
        REFERENCES error_groups (organization_id, id, project_id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id, repository_id, project_id)
        REFERENCES repositories (organization_id, id, project_id) ON DELETE RESTRICT
);

CREATE INDEX remediation_runs_error_group_idx
    ON remediation_runs (organization_id, error_group_id, created_at DESC);

CREATE INDEX remediation_runs_repository_idx
    ON remediation_runs (organization_id, repository_id, project_id);

CREATE TABLE remediation_executions (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    remediation_run_id uuid NOT NULL,
    intent text NOT NULL CHECK (intent IN ('analyze', 'apply_change')),
    attempt integer NOT NULL CHECK (attempt > 0),
    requested_proposal_digest text,
    status text NOT NULL
        CHECK (status IN ('queued', 'running', 'completed', 'failed', 'cancelled')),
    failure jsonb,
    created_at timestamptz NOT NULL DEFAULT now(),
    started_at timestamptz,
    completed_at timestamptz,
    PRIMARY KEY (organization_id, id),
    CHECK (
        (
            intent = 'analyze' AND
            requested_proposal_digest IS NULL
        )
        OR
        (
            intent = 'apply_change' AND
            requested_proposal_digest IS NOT NULL AND
            length(requested_proposal_digest) > 0
        )
    ),
    CHECK (
        failure IS NULL OR
        failure ->> 'schema_version' = 'ariadne.execution-failure.v1'
    ),
    CHECK (
        (status IN ('completed', 'failed', 'cancelled')) =
        (completed_at IS NOT NULL)
    ),
    CHECK ((status = 'failed') = (failure IS NOT NULL)),
    CHECK (started_at IS NULL OR started_at >= created_at),
    CHECK (completed_at IS NULL OR completed_at >= created_at),
    UNIQUE (organization_id, remediation_run_id, intent, attempt),
    FOREIGN KEY (organization_id, remediation_run_id, project_id)
        REFERENCES remediation_runs (organization_id, id, project_id)
        ON DELETE CASCADE,
    FOREIGN KEY (
        organization_id,
        remediation_run_id,
        project_id,
        requested_proposal_digest
    )
        REFERENCES remediation_runs (
            organization_id,
            id,
            project_id,
            proposal_digest
        )
        ON DELETE CASCADE
);

CREATE INDEX remediation_executions_run_idx
    ON remediation_executions (
        organization_id,
        remediation_run_id,
        created_at DESC
    );

CREATE TABLE remediation_run_runtime_targets (
    organization_id uuid NOT NULL,
    remediation_run_id uuid NOT NULL,
    project_id uuid NOT NULL,
    runtime_environment_id uuid NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (
        organization_id,
        remediation_run_id,
        runtime_environment_id
    ),
    FOREIGN KEY (organization_id, remediation_run_id, project_id)
        REFERENCES remediation_runs (organization_id, id, project_id)
        ON DELETE CASCADE,
    FOREIGN KEY (organization_id, runtime_environment_id, project_id)
        REFERENCES runtime_environments (organization_id, id, project_id)
        ON DELETE RESTRICT
);

CREATE INDEX remediation_run_runtime_target_environment_idx
    ON remediation_run_runtime_targets (
        organization_id,
        runtime_environment_id,
        project_id
    );

CREATE TABLE remediation_approvals (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    remediation_run_id uuid NOT NULL,
    actor_user_id uuid NOT NULL,
    proposal_digest text NOT NULL,
    decision text NOT NULL CHECK (decision IN ('approved', 'rejected')),
    comment text,
    decided_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    CHECK (length(proposal_digest) > 0),
    UNIQUE (
        organization_id,
        remediation_run_id,
        proposal_digest,
        actor_user_id
    ),
    FOREIGN KEY (
        organization_id,
        remediation_run_id,
        project_id,
        proposal_digest
    )
        REFERENCES remediation_runs (
            organization_id,
            id,
            project_id,
            proposal_digest
        )
        ON DELETE RESTRICT,
    FOREIGN KEY (organization_id, actor_user_id)
        REFERENCES organization_memberships (organization_id, user_id)
        ON DELETE RESTRICT
);

CREATE INDEX remediation_approvals_run_idx
    ON remediation_approvals (
        organization_id,
        remediation_run_id,
        decided_at DESC
    );

CREATE INDEX remediation_approvals_actor_idx
    ON remediation_approvals (organization_id, actor_user_id);

CREATE TABLE merge_requests (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    project_id uuid NOT NULL,
    remediation_run_id uuid NOT NULL,
    repository_id uuid NOT NULL,
    provider text NOT NULL,
    external_id text NOT NULL,
    url text NOT NULL,
    status text NOT NULL CHECK (status IN ('open', 'merged', 'closed')),
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    UNIQUE (organization_id, repository_id, provider, external_id),
    FOREIGN KEY (organization_id, remediation_run_id, project_id, repository_id)
        REFERENCES remediation_runs (organization_id, id, project_id, repository_id)
        ON DELETE CASCADE
);

CREATE INDEX merge_requests_remediation_run_idx
    ON merge_requests (
        organization_id,
        remediation_run_id,
        project_id,
        repository_id
    );

CREATE TABLE audit_events (
    organization_id uuid NOT NULL,
    id uuid NOT NULL,
    actor_kind text NOT NULL
        CHECK (actor_kind IN ('user', 'system', 'iop', 'ingestion_source')),
    actor_user_id uuid,
    actor_reference text,
    correlation_id text NOT NULL,
    action text NOT NULL,
    resource_type text NOT NULL,
    resource_id text NOT NULL,
    detail jsonb NOT NULL DEFAULT '{}'::jsonb,
    occurred_at timestamptz NOT NULL DEFAULT now(),
    PRIMARY KEY (organization_id, id),
    CHECK (length(correlation_id) > 0),
    CHECK (length(action) > 0),
    CHECK (length(resource_type) > 0),
    CHECK (length(resource_id) > 0),
    CHECK (actor_reference IS NULL OR length(actor_reference) > 0),
    CHECK (
        (
            actor_kind = 'user' AND
            actor_user_id IS NOT NULL AND
            actor_reference IS NULL
        )
        OR
        (
            actor_kind <> 'user' AND
            actor_user_id IS NULL AND
            actor_reference IS NOT NULL
        )
    ),
    FOREIGN KEY (organization_id)
        REFERENCES organizations (id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id, actor_user_id)
        REFERENCES organization_memberships (organization_id, user_id)
        ON DELETE RESTRICT
);

CREATE INDEX audit_events_timeline_idx
    ON audit_events (organization_id, occurred_at DESC);

CREATE INDEX audit_events_actor_user_idx
    ON audit_events (organization_id, actor_user_id)
    WHERE actor_user_id IS NOT NULL;

CREATE INDEX audit_events_correlation_idx
    ON audit_events (organization_id, correlation_id, occurred_at);

CREATE FUNCTION ariadne_current_organization_id()
RETURNS uuid
LANGUAGE sql
STABLE
AS $$
    SELECT NULLIF(current_setting('ariadne.organization_id', true), '')::uuid
$$;

REVOKE ALL ON FUNCTION ariadne_current_organization_id() FROM PUBLIC;

ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations FORCE ROW LEVEL SECURITY;
CREATE POLICY organizations_isolation ON organizations
    USING (id = ariadne_current_organization_id())
    WITH CHECK (id = ariadne_current_organization_id());

ALTER TABLE organization_memberships ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_memberships FORCE ROW LEVEL SECURITY;
CREATE POLICY organization_memberships_isolation ON organization_memberships
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects FORCE ROW LEVEL SECURITY;
CREATE POLICY projects_organization_isolation ON projects
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE ingestion_sources ENABLE ROW LEVEL SECURITY;
ALTER TABLE ingestion_sources FORCE ROW LEVEL SECURITY;
CREATE POLICY ingestion_sources_organization_isolation ON ingestion_sources
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE repositories ENABLE ROW LEVEL SECURITY;
ALTER TABLE repositories FORCE ROW LEVEL SECURITY;
CREATE POLICY repositories_organization_isolation ON repositories
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE runtime_environments ENABLE ROW LEVEL SECURITY;
ALTER TABLE runtime_environments FORCE ROW LEVEL SECURITY;
CREATE POLICY runtime_environments_organization_isolation ON runtime_environments
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE analysis_target_rules ENABLE ROW LEVEL SECURITY;
ALTER TABLE analysis_target_rules FORCE ROW LEVEL SECURITY;
CREATE POLICY analysis_target_rules_organization_isolation ON analysis_target_rules
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE analysis_target_rule_runtime_environments ENABLE ROW LEVEL SECURITY;
ALTER TABLE analysis_target_rule_runtime_environments FORCE ROW LEVEL SECURITY;
CREATE POLICY analysis_target_rule_runtime_environments_isolation
    ON analysis_target_rule_runtime_environments
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE error_groups ENABLE ROW LEVEL SECURITY;
ALTER TABLE error_groups FORCE ROW LEVEL SECURITY;
CREATE POLICY error_groups_organization_isolation ON error_groups
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE error_occurrences ENABLE ROW LEVEL SECURITY;
ALTER TABLE error_occurrences FORCE ROW LEVEL SECURITY;
CREATE POLICY error_occurrences_organization_isolation ON error_occurrences
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE remediation_runs ENABLE ROW LEVEL SECURITY;
ALTER TABLE remediation_runs FORCE ROW LEVEL SECURITY;
CREATE POLICY remediation_runs_organization_isolation ON remediation_runs
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE remediation_executions ENABLE ROW LEVEL SECURITY;
ALTER TABLE remediation_executions FORCE ROW LEVEL SECURITY;
CREATE POLICY remediation_executions_organization_isolation
    ON remediation_executions
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE remediation_run_runtime_targets ENABLE ROW LEVEL SECURITY;
ALTER TABLE remediation_run_runtime_targets FORCE ROW LEVEL SECURITY;
CREATE POLICY remediation_run_runtime_targets_isolation
    ON remediation_run_runtime_targets
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE remediation_approvals ENABLE ROW LEVEL SECURITY;
ALTER TABLE remediation_approvals FORCE ROW LEVEL SECURITY;
CREATE POLICY remediation_approvals_organization_isolation
    ON remediation_approvals
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE merge_requests ENABLE ROW LEVEL SECURITY;
ALTER TABLE merge_requests FORCE ROW LEVEL SECURITY;
CREATE POLICY merge_requests_organization_isolation ON merge_requests
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());

ALTER TABLE audit_events ENABLE ROW LEVEL SECURITY;
ALTER TABLE audit_events FORCE ROW LEVEL SECURITY;
CREATE POLICY audit_events_organization_isolation ON audit_events
    USING (organization_id = ariadne_current_organization_id())
    WITH CHECK (organization_id = ariadne_current_organization_id());
