-- ARR MCP direct structured-result ingestion ledger.
-- PostgreSQL 15+. Additive only; migrations 008 and 009 remain immutable.

BEGIN;

DO $$
BEGIN
    IF current_database() <> 'booking_test' THEN
        RAISE EXCEPTION
            'ARR MCP result ingestion is allowed only in booking_test';
    END IF;
    IF to_regclass('ingestion.processing_runs') IS NULL
       OR to_regclass('ingestion.processing_attempts') IS NULL
       OR to_regclass('finance.daily_versions') IS NULL THEN
        RAISE EXCEPTION
            'ARR MVP v1 migration 008 must be applied first';
    END IF;
    IF to_regclass('ingestion.result_submission_grants') IS NOT NULL
       OR to_regclass('ingestion.result_submissions') IS NOT NULL THEN
        RAISE EXCEPTION
            'ARR MCP result ingestion migration 010 is already applied';
    END IF;
END;
$$;

-- Existing rows retain the artifact callback contract. New MCP runs opt in
-- explicitly and may finish without Agent-produced result objects in OSS.
ALTER TABLE ingestion.processing_runs
    ADD COLUMN result_delivery_mode text NOT NULL
        DEFAULT 'artifact_callback';

ALTER TABLE ingestion.processing_runs
    ADD CONSTRAINT processing_runs_result_delivery_mode_check CHECK (
        result_delivery_mode IN ('artifact_callback', 'direct_mcp')
    ),
    ADD CONSTRAINT processing_runs_result_delivery_pipeline_check CHECK (
        pipeline_type = 'opera_daily'
        OR result_delivery_mode = 'artifact_callback'
    );

ALTER TABLE ingestion.processing_runs
    DROP CONSTRAINT processing_runs_terminal_shape;

ALTER TABLE ingestion.processing_runs
    ADD CONSTRAINT processing_runs_terminal_shape CHECK (
        (
            run_status = 'accepted'
            AND failure_code IS NULL
            AND validated_at IS NOT NULL
            AND finished_at IS NOT NULL
            AND (
                pipeline_type <> 'opera_daily'
                OR (
                    business_date IS NOT NULL
                    AND delivered_processor_version IS NOT NULL
                    AND delivered_rule_set_sha256 IS NOT NULL
                    AND result_schema_version IS NOT NULL
                    AND delivery_sha256 IS NOT NULL
                    AND (
                        (
                            result_delivery_mode = 'artifact_callback'
                            AND result_artifact_id IS NOT NULL
                        )
                        OR (
                            result_delivery_mode = 'direct_mcp'
                            AND result_artifact_id IS NULL
                        )
                    )
                )
            )
        )
        OR (
            run_status IN ('rejected', 'failed')
            AND failure_code IS NOT NULL
            AND finished_at IS NOT NULL
        )
        OR (
            run_status = 'cancelled'
            AND finished_at IS NOT NULL
        )
        OR run_status IN ('received', 'queued', 'running', 'validating')
    );

ALTER TABLE finance.daily_versions
    ADD COLUMN result_delivery_mode text NOT NULL
        DEFAULT 'artifact_callback';

ALTER TABLE finance.daily_versions
    ADD CONSTRAINT daily_versions_result_delivery_mode_check CHECK (
        result_delivery_mode IN ('artifact_callback', 'direct_mcp')
    ),
    ADD CONSTRAINT daily_versions_result_delivery_shape CHECK (
        result_delivery_mode <> 'direct_mcp'
        OR (
            daily_report_artifact_id IS NULL
            AND result_json_artifact_id IS NULL
            AND structured_result_artifact_id IS NULL
            AND exception_report_artifact_id IS NULL
        )
    );

ALTER TABLE finance.daily_versions
    DROP CONSTRAINT daily_versions_status_shape;

ALTER TABLE finance.daily_versions
    ADD CONSTRAINT daily_versions_status_shape CHECK (
        (
            version_status IN ('validated', 'active', 'superseded')
            AND business_date IS NOT NULL
            AND exception_report_artifact_id IS NULL
            AND failure_code IS NULL
            AND validated_at IS NOT NULL
            AND (
                (
                    result_delivery_mode = 'artifact_callback'
                    AND daily_report_artifact_id IS NOT NULL
                    AND result_json_artifact_id IS NOT NULL
                    AND structured_result_artifact_id IS NOT NULL
                )
                OR (
                    result_delivery_mode = 'direct_mcp'
                    AND daily_report_artifact_id IS NULL
                    AND result_json_artifact_id IS NULL
                    AND structured_result_artifact_id IS NULL
                )
            )
        )
        OR (
            version_status = 'rejected'
            AND failure_code IS NOT NULL
        )
    );

CREATE TABLE ingestion.result_submission_grants (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    grant_sha256 character(64) NOT NULL UNIQUE CHECK (
        grant_sha256 ~ '^[0-9a-f]{64}$'
    ),
    processing_run_id bigint NOT NULL
        REFERENCES ingestion.processing_runs(id),
    attempt_id bigint NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    expires_at timestamptz NOT NULL,
    consumed_at timestamptz,
    revoked_at timestamptz,
    CONSTRAINT result_submission_grants_attempt_unique
        UNIQUE (attempt_id),
    CONSTRAINT result_submission_grants_id_run_attempt_unique
        UNIQUE (id, processing_run_id, attempt_id),
    CONSTRAINT result_submission_grants_attempt_run_fk
        FOREIGN KEY (attempt_id, processing_run_id)
        REFERENCES ingestion.processing_attempts(id, processing_run_id),
    CONSTRAINT result_submission_grants_lifetime CHECK (
        expires_at > created_at
        AND expires_at <= created_at + interval '30 minutes'
    ),
    CONSTRAINT result_submission_grants_usage_shape CHECK (
        NOT (consumed_at IS NOT NULL AND revoked_at IS NOT NULL)
        AND (
            consumed_at IS NULL
            OR consumed_at BETWEEN created_at AND expires_at
        )
        AND (revoked_at IS NULL OR revoked_at >= created_at)
    )
);

CREATE INDEX result_submission_grants_expiry_idx
    ON ingestion.result_submission_grants (expires_at)
    WHERE consumed_at IS NULL AND revoked_at IS NULL;

CREATE TABLE ingestion.result_submissions (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    submission_key text NOT NULL UNIQUE CHECK (
        submission_key ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
    ),
    grant_id bigint NOT NULL UNIQUE,
    processing_run_id bigint NOT NULL,
    attempt_id bigint NOT NULL UNIQUE,
    submission_status text NOT NULL CHECK (submission_status IN (
        'received',
        'validating',
        'committed',
        'already_committed',
        'rejected',
        'expired'
    )),
    contract_version text NOT NULL CHECK (
        contract_version = 'arr-direct-ingestion-1'
    ),
    business_date date NOT NULL,
    processor_version text NOT NULL CHECK (
        processor_version ~ '^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$'
    ),
    rule_set_sha256 character(64) NOT NULL CHECK (
        rule_set_sha256 ~ '^[0-9a-f]{64}$'
    ),
    result_schema_version text NOT NULL CHECK (
        btrim(result_schema_version) <> ''
    ),
    payload_json jsonb CHECK (
        payload_json IS NULL
        OR jsonb_typeof(payload_json) = 'object'
    ),
    payload_sha256 character(64) NOT NULL CHECK (
        payload_sha256 ~ '^[0-9a-f]{64}$'
    ),
    payload_byte_size integer NOT NULL CHECK (
        payload_byte_size BETWEEN 2 AND 3145728
    ),
    record_count integer NOT NULL CHECK (
        record_count >= 0
    ),
    created_at timestamptz NOT NULL DEFAULT now(),
    expires_at timestamptz NOT NULL,
    validation_started_at timestamptz,
    finished_at timestamptz,
    payload_purged_at timestamptz,
    daily_version_id bigint
        REFERENCES finance.daily_versions(id),
    failure_code text,
    failure_message text,
    receipt_json jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (
        jsonb_typeof(receipt_json) = 'object'
    ),
    CONSTRAINT result_submissions_grant_run_attempt_fk
        FOREIGN KEY (grant_id, processing_run_id, attempt_id)
        REFERENCES ingestion.result_submission_grants(
            id,
            processing_run_id,
            attempt_id
        ),
    CONSTRAINT result_submissions_lifetime CHECK (
        expires_at > created_at
        AND expires_at <= created_at + interval '30 minutes'
    ),
    CONSTRAINT result_submissions_payload_shape CHECK (
        payload_json IS NULL
        OR (
            payload_json ? 'records'
            AND jsonb_typeof(payload_json -> 'records') = 'array'
            AND jsonb_array_length(payload_json -> 'records') = record_count
        )
    ),
    CONSTRAINT result_submissions_timestamps_valid CHECK (
        (
            validation_started_at IS NULL
            OR validation_started_at >= created_at
        )
        AND (finished_at IS NULL OR finished_at >= created_at)
        AND (
            payload_purged_at IS NULL
            OR (
                finished_at IS NOT NULL
                AND payload_purged_at >= finished_at
            )
        )
        AND (
            submission_status <> 'expired'
            OR finished_at >= expires_at
        )
    ),
    CONSTRAINT result_submissions_status_shape CHECK (
        (
            submission_status = 'received'
            AND validation_started_at IS NULL
            AND finished_at IS NULL
            AND daily_version_id IS NULL
            AND failure_code IS NULL
            AND receipt_json = '{}'::jsonb
            AND payload_json IS NOT NULL
            AND payload_purged_at IS NULL
        )
        OR (
            submission_status = 'validating'
            AND validation_started_at IS NOT NULL
            AND finished_at IS NULL
            AND daily_version_id IS NULL
            AND failure_code IS NULL
            AND receipt_json = '{}'::jsonb
            AND payload_json IS NOT NULL
            AND payload_purged_at IS NULL
        )
        OR (
            submission_status IN ('committed', 'already_committed')
            AND validation_started_at IS NOT NULL
            AND finished_at IS NOT NULL
            AND daily_version_id IS NOT NULL
            AND failure_code IS NULL
            AND receipt_json <> '{}'::jsonb
            AND payload_json IS NULL
            AND payload_purged_at IS NOT NULL
        )
        OR (
            submission_status IN ('rejected', 'expired')
            AND finished_at IS NOT NULL
            AND daily_version_id IS NULL
            AND failure_code IS NOT NULL
            AND btrim(failure_code) <> ''
            AND payload_json IS NULL
            AND payload_purged_at IS NOT NULL
        )
    )
);

CREATE INDEX result_submissions_status_expiry_idx
    ON ingestion.result_submissions (submission_status, expires_at);

-- Deliberately non-unique: the same XML/result may be processed repeatedly by
-- different test jobs while final Finance facts remain idempotent.
CREATE INDEX result_submissions_payload_sha256_idx
    ON ingestion.result_submissions (payload_sha256);

CREATE INDEX result_submissions_daily_version_idx
    ON ingestion.result_submissions (daily_version_id)
    WHERE daily_version_id IS NOT NULL;

COMMENT ON COLUMN ingestion.processing_runs.result_delivery_mode IS
  'artifact_callback preserves the v3 OSS result contract; direct_mcp commits validated structured records without Agent-produced output objects.';

COMMENT ON COLUMN finance.daily_versions.result_delivery_mode IS
  'Identifies whether the validated Finance version arrived through the legacy artifact callback or the direct MCP ingestion path.';

COMMENT ON TABLE ingestion.result_submission_grants IS
  'Hashed, attempt-bound grants for starting one direct MCP result submission. Plaintext grants and database credentials are never stored.';

COMMENT ON TABLE ingestion.result_submissions IS
  'Durable, idempotent single-call result ledger. Payload hashes are intentionally non-unique so the same XML can be rerun under a new processing attempt; guest-bearing payload JSON is purged at terminal status.';

COMMIT;
