-- Gemini models that Google has retired or superseded stay callable only through
-- the compatibility window; they are no longer eligible for new catalog cards.
UPDATE base_model_catalog
SET status = 'deprecated',
    metadata = metadata || jsonb_build_object(
      'officialModelId', invocation_name,
      'officialDisplayName', COALESCE(NULLIF(display_name, ''), invocation_name),
      'lifecycleStage', 'deprecated',
      'replacementModel', CASE
        WHEN invocation_name = 'gemini-2.0-flash-exp-image-generation' THEN 'gemini-3.1-flash-image'
        WHEN invocation_name = 'gemini-3-pro-preview' THEN 'gemini-3.1-pro-preview'
        WHEN invocation_name = 'gemini-3.1-flash-lite-preview' THEN 'gemini-3.5-flash-lite'
        WHEN invocation_name = 'gemini-3-pro-image-preview' THEN 'gemini-3-pro-image'
        WHEN invocation_name = 'gemini-3.1-flash-image-preview' THEN 'gemini-3.1-flash-image'
        WHEN invocation_name = 'gemini-3-flash-preview' THEN 'gemini-3.6-flash'
        ELSE metadata->>'replacementModel'
      END,
      'retireAt', '2026-07-22',
      'sourceUrl', 'https://ai.google.dev/gemini-api/docs/deprecations',
      'verifiedAt', '2026-07-22'
    ),
    updated_at = now()
WHERE catalog_type = 'system'
  AND customized_at IS NULL
  AND invocation_name IN (
    'gemini-2.0-flash-exp-image-generation',
    'gemini-3-pro-preview',
    'gemini-3.1-flash-lite-preview',
    'gemini-3-pro-image-preview',
    'gemini-3.1-flash-image-preview',
    'gemini-3-flash-preview'
  );

WITH targets(model_id, display_name, template_model, fallback_type, lifecycle_stage) AS (
  VALUES
    ('gemini-3.1-pro-preview', 'Gemini 3.1 Pro Preview', 'gemini-3-pro-preview', 'text_generate', 'preview'),
    ('gemini-3.5-flash-lite', 'Gemini 3.5 Flash-Lite', 'gemini-3.1-flash-lite-preview', 'text_generate', 'stable'),
    ('gemini-3-pro-image', 'Gemini 3 Pro Image', 'gemini-3-pro-image-preview', 'image_generate', 'stable'),
    ('gemini-3.1-flash-image', 'Gemini 3.1 Flash Image', 'gemini-3.1-flash-image-preview', 'image_generate', 'stable'),
    ('gemini-3.6-flash', 'Gemini 3.6 Flash', 'gemini-3-flash-preview', 'text_generate', 'stable')
)
INSERT INTO base_model_catalog (
  provider_id, provider_key, canonical_model_key, invocation_name, provider_model_name,
  model_type, display_name, capabilities, base_billing_config, default_rate_limit_policy,
  pricing_rule_set_id, runtime_policy_set_id, runtime_policy_override, metadata,
  catalog_type, pricing_version, status
)
SELECT provider.id,
       'gemini',
       'gemini:' || target.model_id,
       target.model_id,
       target.model_id,
       COALESCE(template.model_type, jsonb_build_array(target.fallback_type)),
       target.display_name,
       COALESCE(template.capabilities, '{}'::jsonb),
       COALESCE(template.base_billing_config, '{}'::jsonb),
       COALESCE(template.default_rate_limit_policy, '{}'::jsonb),
       template.pricing_rule_set_id,
       template.runtime_policy_set_id,
       COALESCE(template.runtime_policy_override, '{}'::jsonb),
       jsonb_build_object(
         'officialModelId', target.model_id,
         'officialDisplayName', target.display_name,
         'lifecycleStage', target.lifecycle_stage,
         'sourceUrl', 'https://ai.google.dev/gemini-api/docs/deprecations',
         'verifiedAt', '2026-07-22',
         'bindingStatus', 'unverified'
       ),
       'system',
       COALESCE(template.pricing_version, 1),
       'active'
FROM targets target
LEFT JOIN model_catalog_providers provider ON provider.provider_key = 'gemini' OR provider.provider_code = 'gemini'
LEFT JOIN LATERAL (
  SELECT source.model_type, source.capabilities, source.base_billing_config,
         source.default_rate_limit_policy, source.pricing_rule_set_id,
         source.runtime_policy_set_id, source.runtime_policy_override, source.pricing_version
  FROM base_model_catalog source
  WHERE source.invocation_name = target.template_model
  ORDER BY CASE WHEN source.provider_key = 'gemini' THEN 0 ELSE 1 END, source.updated_at DESC
  LIMIT 1
) template ON true
ON CONFLICT (canonical_model_key) DO UPDATE
SET invocation_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.invocation_name ELSE base_model_catalog.invocation_name END,
    provider_model_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.provider_model_name ELSE base_model_catalog.provider_model_name END,
    display_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.display_name ELSE base_model_catalog.display_name END,
    metadata = CASE WHEN base_model_catalog.customized_at IS NULL THEN base_model_catalog.metadata || EXCLUDED.metadata ELSE base_model_catalog.metadata END,
    status = CASE WHEN base_model_catalog.customized_at IS NULL THEN 'active' ELSE base_model_catalog.status END,
    updated_at = now();

-- Alibaba Cloud announced the following Qwen IDs for retirement by 2026-10-10.
UPDATE base_model_catalog
SET status = 'deprecated',
    metadata = metadata || jsonb_build_object(
      'officialModelId', invocation_name,
      'officialDisplayName', COALESCE(NULLIF(display_name, ''), invocation_name),
      'lifecycleStage', 'deprecated',
      'replacementModel', CASE
        WHEN invocation_name = 'qwen3-max' THEN 'qwen3.7-max'
        WHEN invocation_name = 'qwen-turbo' THEN 'qwen3.7-plus'
        WHEN invocation_name IN ('qwen-vl-max', 'qwen-vl-plus') THEN 'qwen3.6-flash'
        WHEN invocation_name = 'qwen3-235b-a22b' THEN 'qwen3.7-plus'
        ELSE metadata->>'replacementModel'
      END,
      'retireAt', '2026-10-10',
      'sourceUrl', 'https://help.aliyun.com/en/model-studio/model-depreciation',
      'verifiedAt', '2026-07-22'
    ),
    updated_at = now()
WHERE catalog_type = 'system'
  AND customized_at IS NULL
  AND invocation_name IN ('qwen3-max', 'qwen-turbo', 'qwen-vl-max', 'qwen-vl-plus', 'qwen3-235b-a22b');

WITH targets(model_id, display_name, template_model, fallback_type) AS (
  VALUES
    ('qwen3.7-max', 'Qwen3.7-Max', 'qwen3-max', 'text_generate'),
    ('qwen3.7-plus', 'Qwen3.7-Plus', 'qwen3-235b-a22b', 'text_generate'),
    ('qwen3.6-flash', 'Qwen3.6-Flash', 'qwen-vl-plus', 'text_generate')
)
INSERT INTO base_model_catalog (
  provider_id, provider_key, canonical_model_key, invocation_name, provider_model_name,
  model_type, display_name, capabilities, base_billing_config, default_rate_limit_policy,
  pricing_rule_set_id, runtime_policy_set_id, runtime_policy_override, metadata,
  catalog_type, pricing_version, status
)
SELECT provider.id,
       'aliyun',
       'aliyun:' || target.model_id,
       target.model_id,
       target.model_id,
       COALESCE(template.model_type, jsonb_build_array(target.fallback_type)),
       target.display_name,
       COALESCE(template.capabilities, '{}'::jsonb),
       COALESCE(template.base_billing_config, '{}'::jsonb),
       COALESCE(template.default_rate_limit_policy, '{}'::jsonb),
       template.pricing_rule_set_id,
       template.runtime_policy_set_id,
       COALESCE(template.runtime_policy_override, '{}'::jsonb),
       jsonb_build_object(
         'officialModelId', target.model_id,
         'officialDisplayName', target.display_name,
         'lifecycleStage', 'stable',
         'sourceUrl', 'https://help.aliyun.com/en/model-studio/model-depreciation',
         'verifiedAt', '2026-07-22',
         'bindingStatus', 'unverified'
       ),
       'system',
       COALESCE(template.pricing_version, 1),
       'active'
FROM targets target
LEFT JOIN model_catalog_providers provider ON provider.provider_key = 'aliyun' OR provider.provider_code = 'aliyun'
LEFT JOIN LATERAL (
  SELECT source.model_type, source.capabilities, source.base_billing_config,
         source.default_rate_limit_policy, source.pricing_rule_set_id,
         source.runtime_policy_set_id, source.runtime_policy_override, source.pricing_version
  FROM base_model_catalog source
  WHERE source.invocation_name = target.template_model
  ORDER BY CASE WHEN source.provider_key = 'aliyun' THEN 0 ELSE 1 END, source.updated_at DESC
  LIMIT 1
) template ON true
ON CONFLICT (canonical_model_key) DO UPDATE
SET invocation_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.invocation_name ELSE base_model_catalog.invocation_name END,
    provider_model_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.provider_model_name ELSE base_model_catalog.provider_model_name END,
    display_name = CASE WHEN base_model_catalog.customized_at IS NULL THEN EXCLUDED.display_name ELSE base_model_catalog.display_name END,
    metadata = CASE WHEN base_model_catalog.customized_at IS NULL THEN base_model_catalog.metadata || EXCLUDED.metadata ELSE base_model_catalog.metadata END,
    status = CASE WHEN base_model_catalog.customized_at IS NULL THEN 'active' ELSE base_model_catalog.status END,
    updated_at = now();

-- DeepSeek direct API has two official public IDs. Aggregator-only V4 names are
-- kept but explicitly classified as provider identities.
UPDATE base_model_catalog
SET metadata = metadata || jsonb_build_object(
      'officialModelId', invocation_name,
      'officialDisplayName', COALESCE(NULLIF(display_name, ''), invocation_name),
      'lifecycleStage', 'stable',
      'sourceUrl', 'https://api-docs.deepseek.com/quick_start/pricing-details-usd',
      'verifiedAt', '2026-07-22',
      'identityAuthority', 'official'
    ),
    updated_at = now()
WHERE invocation_name IN ('deepseek-chat', 'deepseek-reasoner');

UPDATE base_model_catalog
SET metadata = metadata || jsonb_build_object(
      'lifecycleStage', 'provider',
      'verifiedAt', '2026-07-22',
      'identityAuthority', 'provider'
    ),
    updated_at = now()
WHERE lower(invocation_name) LIKE 'deepseek-v4-%';

-- OpenRouter keeps its namespaced upstream ID, while callers use Anthropic's
-- official model ID.
WITH claude_mapping(provider_model_name, invocation_name, display_name) AS (
  VALUES
    ('anthropic/claude-opus-4.5', 'claude-opus-4-5', 'Claude Opus 4.5'),
    ('anthropic/claude-opus-4.6', 'claude-opus-4-6', 'Claude Opus 4.6'),
    ('anthropic/claude-sonnet-4.5', 'claude-sonnet-4-5', 'Claude Sonnet 4.5'),
    ('anthropic/claude-sonnet-4.6', 'claude-sonnet-4-6', 'Claude Sonnet 4.6'),
    ('anthropic/claude-haiku-4.5', 'claude-haiku-4-5', 'Claude Haiku 4.5')
), affected AS (
  SELECT base.id, base.invocation_name AS old_invocation_name, mapping.invocation_name, mapping.display_name
  FROM base_model_catalog base
  JOIN claude_mapping mapping ON mapping.provider_model_name = base.provider_model_name
  WHERE base.catalog_type = 'system' AND base.customized_at IS NULL
), old_aliases AS (
  INSERT INTO model_compatibility_aliases (base_model_id, alias, model_type, expires_at)
  SELECT affected.id, affected.old_invocation_name, model_type.value, now() + interval '14 days'
  FROM affected
  JOIN base_model_catalog base ON base.id = affected.id
  CROSS JOIN LATERAL jsonb_array_elements_text(base.model_type) AS model_type(value)
  WHERE affected.old_invocation_name <> affected.invocation_name
  ON CONFLICT (base_model_id, alias, model_type) DO NOTHING
)
UPDATE base_model_catalog base
SET invocation_name = affected.invocation_name,
    display_name = affected.display_name,
    metadata = base.metadata || jsonb_build_object(
      'officialModelId', affected.invocation_name,
      'officialDisplayName', affected.display_name,
      'lifecycleStage', 'stable',
      'sourceUrl', 'https://platform.claude.com/docs/en/about-claude/models/model-ids-and-versions',
      'verifiedAt', '2026-07-22',
      'identityAuthority', 'official'
    ),
    updated_at = now()
FROM affected
WHERE base.id = affected.id;

WITH ranked_targets AS (
  SELECT platform_model.id,
         base.invocation_name,
         COALESCE(NULLIF(base.display_name, ''), base.invocation_name) AS display_name,
         row_number() OVER (
           PARTITION BY platform_model.platform_id, base.invocation_name
           ORDER BY CASE WHEN platform_model.model_name = base.invocation_name THEN 0 ELSE 1 END,
                    platform_model.created_at ASC,
                    platform_model.id ASC
         ) AS identity_rank
  FROM platform_models platform_model
  JOIN base_model_catalog base ON base.id = platform_model.base_model_id
)
UPDATE platform_models platform_model
SET model_name = target.invocation_name,
    model_alias = target.invocation_name,
    display_name = target.display_name,
    updated_at = now()
FROM ranked_targets target
WHERE target.id = platform_model.id
  AND target.identity_rank = 1
  AND platform_model.model_name <> target.invocation_name
  AND NOT EXISTS (
    SELECT 1
    FROM platform_models conflict
    WHERE conflict.platform_id = platform_model.platform_id
      AND conflict.id <> platform_model.id
      AND conflict.model_name = target.invocation_name
  );

-- MiniMax keeps current main and fallback families; older text/speech previews
-- remain present only for compatibility and reference-safe migration.
UPDATE base_model_catalog
SET status = 'deprecated',
    metadata = metadata || jsonb_build_object(
      'lifecycleStage', 'deprecated',
      'replacementModel', CASE
        WHEN lower(invocation_name) LIKE '%speech-2.5%' THEN 'speech-2.8'
        ELSE 'minimax-m2.5'
      END,
      'sourceUrl', 'https://platform.minimaxi.com/docs/api-reference/api-overview',
      'verifiedAt', '2026-07-22'
    ),
    updated_at = now()
WHERE catalog_type = 'system'
  AND customized_at IS NULL
  AND (
    lower(invocation_name) IN ('minimax-m2', 'minimax-m2.1', 'minimax-m2-highspeed', 'minimax-m2.1-highspeed')
    OR lower(invocation_name) LIKE '%speech-2.5%preview%'
  );

-- Consolidate duplicate qwen-max rows conservatively. A row is hidden only
-- after every safe platform binding has moved to the selected keeper.
WITH ranked AS (
  SELECT base.id,
         first_value(base.id) OVER (
           PARTITION BY base.provider_key, base.invocation_name
           ORDER BY (SELECT count(*) FROM platform_models model WHERE model.base_model_id = base.id) DESC,
                    CASE WHEN base.customized_at IS NULL THEN 0 ELSE 1 END,
                    base.created_at ASC,
                    base.id ASC
         ) AS keeper_id
  FROM base_model_catalog base
  WHERE lower(base.invocation_name) = 'qwen-max'
), rebound AS (
  UPDATE platform_models model
  SET base_model_id = ranked.keeper_id,
      updated_at = now()
  FROM ranked
  WHERE model.base_model_id = ranked.id
    AND ranked.id <> ranked.keeper_id
    AND NOT EXISTS (
      SELECT 1 FROM platform_models keeper_model
      WHERE keeper_model.platform_id = model.platform_id
        AND keeper_model.base_model_id = ranked.keeper_id
    )
  RETURNING model.id
)
UPDATE base_model_catalog base
SET status = 'hidden',
    metadata = metadata || jsonb_build_object(
      'lifecycleStage', 'duplicate',
      'cleanupCandidate', true,
      'verifiedAt', '2026-07-22'
    ),
    updated_at = now()
FROM ranked
WHERE base.id = ranked.id
  AND ranked.id <> ranked.keeper_id
  AND base.catalog_type = 'system'
  AND base.customized_at IS NULL
  AND NOT EXISTS (SELECT 1 FROM platform_models model WHERE model.base_model_id = base.id);

-- Release B may physically delete only candidates which remain unreferenced
-- after the observation window. Custom and manually modified rows are excluded.
UPDATE base_model_catalog base
SET metadata = metadata || jsonb_build_object(
      'cleanupCandidate', true,
      'cleanupEligibleAfter', '2026-08-05'
    ),
    updated_at = now()
WHERE base.status IN ('deprecated', 'hidden')
  AND base.catalog_type = 'system'
  AND base.customized_at IS NULL
  AND NOT EXISTS (SELECT 1 FROM platform_models model WHERE model.base_model_id = base.id);
