-- Repair duplicate canonical asset keys without deleting historical rows.
-- Safety: bounded quarantine, transactional index rebuild, independent verifier.

SET lock_timeout = '10s';
SET statement_timeout = '90s';
SET enable_indexscan = 'off';
SET enable_indexonlyscan = 'off';
SET enable_bitmapscan = 'off';

CREATE TEMP TABLE asset_inventory_integrity_repair_map
ON COMMIT DROP
AS
WITH ranked_assets AS (
    SELECT
        asset_id,
        asset_key AS original_asset_key,
        FIRST_VALUE(asset_id) OVER (
            PARTITION BY asset_key
            ORDER BY first_seen_at ASC, asset_id ASC
        ) AS canonical_asset_id,
        ROW_NUMBER() OVER (
            PARTITION BY asset_key
            ORDER BY first_seen_at ASC, asset_id ASC
        ) AS duplicate_rank
    FROM asset_inventory
)
SELECT
    asset_id,
    original_asset_key,
    canonical_asset_id
FROM ranked_assets
WHERE duplicate_rank > 1;

DO $$
DECLARE
    repair_row_count BIGINT;
BEGIN
    SELECT COUNT(*) INTO repair_row_count
    FROM asset_inventory_integrity_repair_map;

    IF repair_row_count > 100 THEN
        RAISE EXCEPTION
            'asset_inventory_integrity_repair_bound_exceeded: % rows',
            repair_row_count;
    END IF;
END
$$;

UPDATE asset_inventory AS duplicate_asset
SET
    asset_key = duplicate_asset.asset_key
        || '/__integrity_quarantine__/'
        || duplicate_asset.asset_id::TEXT,
    lifecycle_state = 'deprecated',
    decommissioned_at = COALESCE(duplicate_asset.decommissioned_at, NOW()),
    metadata = COALESCE(duplicate_asset.metadata, '{}'::JSONB)
        || JSONB_BUILD_OBJECT(
            'integrity_state', 'quarantined_duplicate',
            'integrity_canonical_asset_id', repair.canonical_asset_id,
            'integrity_repair', 'asset_inventory_canonical_integrity_2026-07-14'
        ),
    tags = CASE
        WHEN 'integrity:quarantined_duplicate' = ANY(duplicate_asset.tags)
            THEN duplicate_asset.tags
        ELSE ARRAY_APPEND(
            duplicate_asset.tags,
            'integrity:quarantined_duplicate'
        )
    END,
    updated_at = NOW()
FROM asset_inventory_integrity_repair_map AS repair
WHERE duplicate_asset.asset_id = repair.asset_id;

-- Rebuild the unique index after every formerly unindexed duplicate has a
-- distinct quarantine key. The outer CD transaction rolls back on any error.
REINDEX INDEX asset_inventory_asset_key_key;

DO $$
DECLARE
    duplicate_group_count BIGINT;
    repaired_row_count BIGINT;
    expected_repair_count BIGINT;
    index_healthy BOOLEAN;
BEGIN
    SELECT COUNT(*) INTO duplicate_group_count
    FROM (
        SELECT asset_key
        FROM asset_inventory
        GROUP BY asset_key
        HAVING COUNT(*) > 1
    ) AS duplicate_groups;

    SELECT COUNT(*) INTO expected_repair_count
    FROM asset_inventory_integrity_repair_map;

    SELECT COUNT(*) INTO repaired_row_count
    FROM asset_inventory AS repaired
    JOIN asset_inventory_integrity_repair_map AS repair
      ON repair.asset_id = repaired.asset_id
    WHERE repaired.lifecycle_state = 'deprecated'
      AND repaired.asset_key = repair.original_asset_key
          || '/__integrity_quarantine__/'
          || repaired.asset_id::TEXT
      AND repaired.metadata ->> 'integrity_state' = 'quarantined_duplicate';

    SELECT
        index_row.indisvalid
        AND index_row.indisready
        AND index_row.indislive
        AND index_row.indisunique
    INTO index_healthy
    FROM pg_index AS index_row
    WHERE index_row.indexrelid = 'asset_inventory_asset_key_key'::REGCLASS;

    IF duplicate_group_count <> 0 THEN
        RAISE EXCEPTION
            'asset_inventory_duplicate_groups_remain: %',
            duplicate_group_count;
    END IF;

    IF repaired_row_count <> expected_repair_count THEN
        RAISE EXCEPTION
            'asset_inventory_quarantine_verifier_failed: expected %, got %',
            expected_repair_count,
            repaired_row_count;
    END IF;

    IF index_healthy IS DISTINCT FROM TRUE THEN
        RAISE EXCEPTION 'asset_inventory_unique_index_not_healthy';
    END IF;
END
$$;

-- Exercise the exact conflict-update path that failed before the repair while
-- preserving every application value.
WITH probe AS (
    SELECT
        asset_key,
        asset_type,
        host,
        namespace,
        name,
        metadata,
        tags,
        environment,
        lifecycle_state,
        first_seen_at,
        last_seen_at
    FROM asset_inventory
    ORDER BY asset_id
    LIMIT 1
)
INSERT INTO asset_inventory (
    asset_key,
    asset_type,
    host,
    namespace,
    name,
    metadata,
    tags,
    environment,
    lifecycle_state,
    first_seen_at,
    last_seen_at
)
SELECT
    asset_key,
    asset_type,
    host,
    namespace,
    name,
    metadata,
    tags,
    environment,
    lifecycle_state,
    first_seen_at,
    last_seen_at
FROM probe
ON CONFLICT (asset_key) DO UPDATE
SET
    last_seen_at = asset_inventory.last_seen_at,
    updated_at = asset_inventory.updated_at;

INSERT INTO automation_operation_log (
    operation_type,
    actor,
    status,
    input,
    output,
    tags
)
SELECT
    'asset_discovered',
    'gitea_cd_asset_integrity_reconciler',
    'success',
    '{"work_item_id":"AIA-P0-006","migration":"asset_inventory_canonical_integrity_2026-07-14"}'::JSONB,
    JSONB_BUILD_OBJECT(
        'duplicate_group_count_before', COUNT(DISTINCT original_asset_key),
        'rows_quarantined', COUNT(*),
        'duplicate_group_count_after', 0,
        'index_rebuilt', TRUE,
        'canonical_upsert_verified', TRUE,
        'data_rows_deleted', 0
    ),
    ARRAY[
        'asset_control_plane',
        'integrity_repair',
        'controlled_apply',
        'post_verified',
        'no_delete'
    ]
FROM asset_inventory_integrity_repair_map;

COMMENT ON INDEX asset_inventory_asset_key_key IS
    'Canonical asset identity unique index; transactionally rebuilt and verified by Gitea CD.';
