"""候選審核 queue review_state writer preflight。

本模組只檢查 review_state writer status、transaction payload 與 catalog
欄位合約；不讀 token、不更新 review_state、不 commit、不掛 scheduler。
"""

from sqlalchemy import inspect


TARGET_TABLE = "market_alert_review_queue"
TARGET_OPERATION = "update_review_state"
ALLOWED_DECISIONS = ("confirmed", "rejected", "deferred")
FORBIDDEN_TOKEN_KEYWORDS = (
    "approval_token",
    "approval-token",
    "market_intel_queue_write_approval",
)
SAFE_TOKEN_METADATA_KEYS = {
    "approval_token_present",
    "approval_token_valid",
    "approval_token_secret_configured",
}
APPROVAL_ENV_VAR = "MARKET_INTEL_QUEUE_WRITE_APPROVAL"
PAYLOAD_COLUMN_MAP = {
    "dedupe_key": "dedupe_key",
    "next_review_state": "review_state",
}
REQUIRED_UPDATE_COLUMNS = [
    "dedupe_key",
    "review_state",
    "reviewed_at",
    "updated_at",
]
BLOCKED_WRITE_FLAGS = (
    "ready_for_api_database_write",
    "ready_for_scheduler_attach",
    "review_state_update_allowed_from_api",
    "api_executes_cli",
    "api_reads_approval_token",
    "api_writes_file",
    "api_writes_database",
    "api_updates_review_state",
    "approval_record_written",
    "decision_record_written",
    "review_state_update_executed",
    "database_connection_opened",
    "database_session_created",
    "explicit_transaction_opened",
    "transaction_opened",
    "transaction_committed",
    "database_write_executed",
    "database_commit_executed",
    "database_rollback_executed",
    "scheduler_attached",
    "writes_executed",
    "would_write_database",
)


def _as_dict(value):
    return value if isinstance(value, dict) else {}


def _as_list(value):
    if value is None:
        return []
    if isinstance(value, (list, tuple, set)):
        return list(value)
    return [value]


def _contains_forbidden_token_key(value):
    if isinstance(value, dict):
        for key, nested in value.items():
            normalized_key = str(key).lower()
            if normalized_key in SAFE_TOKEN_METADATA_KEYS and isinstance(nested, bool):
                continue
            if normalized_key == "approval_env_var" and nested == APPROVAL_ENV_VAR:
                continue
            if any(token_key in normalized_key for token_key in FORBIDDEN_TOKEN_KEYWORDS):
                return True
            if _contains_forbidden_token_key(nested):
                return True
    elif isinstance(value, list):
        return any(_contains_forbidden_token_key(item) for item in value)
    return False


def _transaction_statement_payloads(transaction_preview):
    payloads = []
    for statement in _as_list(_as_dict(transaction_preview).get("statements")):
        statement = _as_dict(statement)
        parameter_preview = _as_dict(statement.get("parameter_preview"))
        lookup = _as_dict(statement.get("lookup"))
        dedupe_key = (
            parameter_preview.get("dedupe_key")
            or lookup.get("dedupe_key")
            or statement.get("dedupe_key")
        )
        current_state = (
            parameter_preview.get("expected_current_review_state")
            or statement.get("expected_current_review_state")
        )
        next_state = (
            parameter_preview.get("next_review_state")
            or statement.get("next_review_state")
        )
        payloads.append(
            {
                "idempotency_key": statement.get("idempotency_key"),
                "dedupe_key": dedupe_key,
                "expected_current_review_state": current_state,
                "next_review_state": next_state,
                "statement_type": statement.get("statement_type")
                or "update_review_state",
                "operation": statement.get("operation") or "update",
                "parameter_payload_hash": statement.get("parameter_payload_hash"),
                "write_status": "blocked_preflight_only",
            }
        )
    return payloads


def _statement_payloads(writer_status, transaction_preview):
    writer_status = _as_dict(writer_status)
    payloads = _as_list(writer_status.get("statement_payloads"))
    if payloads:
        return [_as_dict(item) for item in payloads]

    payloads = (
        _as_dict(writer_status)
        .get("statement_summary", {})
        .get("review_state_updates", [])
    )
    if payloads:
        return [_as_dict(item) for item in _as_list(payloads)]

    return _transaction_statement_payloads(transaction_preview)


def _validate_payloads(payloads):
    invalid = []
    normalized = []
    for payload in payloads:
        payload = _as_dict(payload)
        dedupe_key = payload.get("dedupe_key")
        current_state = payload.get("expected_current_review_state")
        next_state = payload.get("next_review_state")
        row = {
            "idempotency_key": payload.get("idempotency_key"),
            "dedupe_key": dedupe_key,
            "expected_current_review_state": current_state,
            "next_review_state": next_state,
            "statement_type": payload.get("statement_type") or "update_review_state",
            "operation": payload.get("operation") or "update",
            "parameter_payload_hash": payload.get("parameter_payload_hash"),
            "write_status": "blocked_preflight_only",
        }
        missing_fields = [
            key
            for key in (
                "dedupe_key",
                "expected_current_review_state",
                "next_review_state",
            )
            if not row.get(key)
        ]
        invalid_transition = bool(
            current_state != "needs_review" or next_state not in ALLOWED_DECISIONS
        )
        invalid_operation = bool(
            row["operation"] != "update"
            or row["statement_type"] != "update_review_state"
        )
        if missing_fields or invalid_transition or invalid_operation:
            invalid.append(
                {
                    "idempotency_key": row["idempotency_key"],
                    "dedupe_key": dedupe_key,
                    "missing_fields": missing_fields,
                    "transition_invalid": invalid_transition,
                    "operation_invalid": invalid_operation,
                }
            )
        normalized.append(row)
    return normalized, invalid


def _writer_status_safe(writer_status):
    writer_status = _as_dict(writer_status)
    if not writer_status:
        return True
    return bool(
        writer_status.get("mode")
        == "candidate_queue_review_decision_writer_cli_blocked"
        and all(not writer_status.get(key) for key in BLOCKED_WRITE_FLAGS)
    )


def _operator_summary(operator_evidence):
    operator_evidence = _as_dict(operator_evidence)
    return {
        "provided_keys": sorted(operator_evidence.keys()),
        "approval_token_submitted_to_api": _contains_forbidden_token_key(
            operator_evidence
        ),
        "operator_confirmed_no_api_db_write": bool(
            operator_evidence.get("operator_confirmed_no_api_db_write")
        ),
        "operator_confirmed_no_scheduler_attach": bool(
            operator_evidence.get("operator_confirmed_no_scheduler_attach")
        ),
        "operator_confirmed_review_state_preflight_only": bool(
            operator_evidence.get("operator_confirmed_review_state_preflight_only")
        ),
    }


def _planned_catalog_probe(database_type):
    return {
        "mode": "planned_only",
        "database_type": database_type or "unknown",
        "table_exists": False,
        "schema_ready": False,
        "read_only_query_executed": False,
        "database_connection_opened": False,
        "columns": [],
        "missing_update_columns": list(REQUIRED_UPDATE_COLUMNS),
        "dedupe_unique_index_present": False,
        "required_columns": list(REQUIRED_UPDATE_COLUMNS),
        "required_unique_lookup": "dedupe_key",
        "next_stage": "manual_shell_catalog_probe_before_writer_enable",
    }


def _read_only_catalog_probe(engine, database_type):
    probe = _planned_catalog_probe(database_type)
    probe["mode"] = "read_only"
    probe["read_only_query_executed"] = True
    try:
        with engine.connect() as connection:
            probe["database_connection_opened"] = True
            inspector = inspect(connection)
            table_exists = TARGET_TABLE in set(inspector.get_table_names())
            probe["table_exists"] = table_exists
            if table_exists:
                columns = [
                    str(column.get("name"))
                    for column in inspector.get_columns(TARGET_TABLE)
                ]
                indexes = inspector.get_indexes(TARGET_TABLE)
                try:
                    unique_constraints = inspector.get_unique_constraints(
                        TARGET_TABLE
                    )
                except Exception:
                    unique_constraints = []
                unique_column_sets = [
                    set(index.get("column_names") or [])
                    for index in indexes
                    if index.get("unique")
                ]
                unique_column_sets.extend(
                    set(constraint.get("column_names") or [])
                    for constraint in unique_constraints
                )
                probe["columns"] = columns
                probe["missing_update_columns"] = [
                    column
                    for column in REQUIRED_UPDATE_COLUMNS
                    if column not in columns
                ]
                probe["dedupe_unique_index_present"] = any(
                    "dedupe_key" in column_set for column_set in unique_column_sets
                )
            probe["schema_ready"] = bool(
                probe["table_exists"]
                and not probe["missing_update_columns"]
                and probe["dedupe_unique_index_present"]
            )
    except Exception as exc:
        probe["error"] = str(exc)[:300]
    return probe


def _safety_contract():
    return {
        "target_table": TARGET_TABLE,
        "target_operation": TARGET_OPERATION,
        "valid_current_review_state": "needs_review",
        "allowed_next_states": list(ALLOWED_DECISIONS),
        "read_only_payload_check_only": True,
        "does_not_read_approval_token": True,
        "does_not_update_review_state": True,
        "does_not_commit_transaction": True,
        "does_not_attach_scheduler": True,
    }


def build_candidate_queue_review_decision_writer_preflight(
    *,
    transaction_preview,
    writer_status=None,
    operator_evidence=None,
    execute_requested=False,
    apply_real_write=False,
    engine=None,
    database_type=None,
):
    """建立 review_state writer preflight；不執行 update 或 commit。"""
    writer_status = _as_dict(writer_status)
    transaction_preview = _as_dict(transaction_preview)
    operator_summary = _operator_summary(operator_evidence)
    statement_payloads, invalid_payloads = _validate_payloads(
        _statement_payloads(writer_status, transaction_preview)
    )
    catalog_probe = (
        _read_only_catalog_probe(engine, database_type)
        if execute_requested and engine is not None
        else _planned_catalog_probe(database_type)
    )
    read_only_probe_loaded = bool(catalog_probe["read_only_query_executed"])
    transaction_summary = writer_status.get("transaction_preview_summary") or {}
    transaction_ready = bool(
        transaction_summary.get("transaction_ready")
        or transaction_preview.get("transaction_ready")
    )
    payload_ready = bool(statement_payloads and not invalid_payloads)
    writer_safe = _writer_status_safe(writer_status)
    writer_status_loaded = bool(writer_status)
    writer_implementation_enabled = bool(
        writer_status.get("writer_implementation_enabled")
    )
    schema_ready = bool(catalog_probe["schema_ready"])
    ready_for_writer_review = bool(
        read_only_probe_loaded
        and schema_ready
        and transaction_ready
        and payload_ready
        and writer_safe
        and not operator_summary["approval_token_submitted_to_api"]
        and not apply_real_write
    )
    if read_only_probe_loaded:
        mode = "candidate_queue_review_decision_writer_preflight_read_only"
    elif writer_status_loaded:
        mode = "candidate_queue_review_decision_writer_preflight_preview"
    else:
        mode = "candidate_queue_review_decision_writer_preflight_planned"
    gates = [
        {
            "key": "writer_status_is_blocked_cli_gate",
            "label": "上一階段 writer status 必須停在 blocked CLI gate 或未載入",
            "passed": writer_safe,
        },
        {
            "key": "transaction_preview_ready",
            "label": "review_state transaction preview 必須已通過人工 gate",
            "passed": transaction_ready,
        },
        {
            "key": "statement_payloads_present",
            "label": "preflight 必須收到 update_review_state statement payload",
            "passed": bool(statement_payloads),
        },
        {
            "key": "statement_payloads_valid",
            "label": "statement payload 必須符合 needs_review 轉人工決策狀態",
            "passed": payload_ready,
        },
        {
            "key": "preflight_no_token_submitted_to_api",
            "label": "operator_evidence 不得包含一次性 approval token key",
            "passed": not operator_summary["approval_token_submitted_to_api"],
        },
        {
            "key": "preflight_execute_not_requested_from_api",
            "label": "API preflight 不執行 shell/DB 動作；只讀 probe 需注入 engine",
            "passed": bool(not execute_requested or engine is not None),
        },
        {
            "key": "preflight_apply_real_write_not_requested_from_api",
            "label": "API preflight 不接受 apply_real_write",
            "passed": not apply_real_write,
        },
        {
            "key": "review_decision_writer_preflight_read_only_probe_loaded",
            "label": "正式 writer 前需人工觸發只讀 catalog probe",
            "passed": read_only_probe_loaded,
        },
        {
            "key": "review_decision_writer_preflight_schema_ready",
            "label": "catalog 必須具備 review_state update 必要欄位與 dedupe unique index",
            "passed": schema_ready,
        },
        {
            "key": "review_decision_writer_implementation_enabled",
            "label": "review_state writer 實作尚未啟用，本階段只做 preflight",
            "passed": bool(not writer_status_loaded or writer_implementation_enabled),
        },
    ]
    blocked_reasons = [gate["key"] for gate in gates if not gate["passed"]]
    if not read_only_probe_loaded:
        blocked_reasons.append("review_decision_writer_preflight_not_loaded")
    if ready_for_writer_review:
        blocked_reasons = []

    return {
        "mode": mode,
        "target_table": TARGET_TABLE,
        "target_operation": TARGET_OPERATION,
        "execute_requested": bool(execute_requested),
        "apply_real_write_requested": bool(apply_real_write),
        "preflight_payload_ready": payload_ready,
        "writer_status_safe": writer_safe,
        "writer_implementation_enabled": writer_implementation_enabled,
        "preflight_ready": ready_for_writer_review,
        "ready_for_writer_review": ready_for_writer_review,
        "ready_for_review_state_writer_review": ready_for_writer_review,
        "ready_for_real_write": False,
        "ready_for_api_review_state_update": False,
        "ready_for_api_database_write": False,
        "ready_for_scheduler_attach": False,
        "review_state_update_allowed_from_api": False,
        "api_executes_cli": False,
        "api_reads_approval_token": False,
        "api_writes_file": False,
        "api_writes_database": False,
        "api_updates_review_state": False,
        "approval_record_written": False,
        "decision_record_written": False,
        "review_state_update_executed": False,
        "read_only_query_executed": bool(catalog_probe["read_only_query_executed"]),
        "database_connection_opened": bool(
            catalog_probe["database_connection_opened"]
        ),
        "database_session_created": False,
        "explicit_transaction_opened": False,
        "transaction_opened": False,
        "transaction_committed": False,
        "database_write_executed": False,
        "database_commit_executed": False,
        "database_rollback_executed": False,
        "external_network_executed": False,
        "scheduler_attached": False,
        "writes_executed": False,
        "would_write_database": False,
        "statement_count": len(statement_payloads),
        "invalid_statement_count": len(invalid_payloads),
        "invalid_statements": invalid_payloads,
        "payload_column_map": dict(PAYLOAD_COLUMN_MAP),
        "mapped_update_columns": list(PAYLOAD_COLUMN_MAP.values()),
        "required_update_columns": list(REQUIRED_UPDATE_COLUMNS),
        "unmapped_payload_keys": [],
        "table_exists": bool(catalog_probe["table_exists"]),
        "schema_ready": schema_ready,
        "missing_update_columns": list(catalog_probe["missing_update_columns"]),
        "dedupe_unique_index_present": bool(
            catalog_probe["dedupe_unique_index_present"]
        ),
        "blocked_reasons": blocked_reasons,
        "preflight_gates": gates,
        "operator_preflight_summary": operator_summary,
        "statement_summary": {
            "statement_count": len(statement_payloads),
            "invalid_statement_count": len(invalid_payloads),
            "invalid_statements": invalid_payloads,
            "allowed_next_states": list(ALLOWED_DECISIONS),
            "review_state_updates": statement_payloads,
        },
        "transaction_preview_summary": {
            "mode": transaction_summary.get("mode") or transaction_preview.get("mode"),
            "transaction_ready": transaction_ready,
            "statement_count": (
                transaction_summary.get("statement_count")
                or len(_as_list(transaction_preview.get("statements")))
            ),
            "api_write_allowed": False,
        },
        "update_contract": {
            "target_table": TARGET_TABLE,
            "operation": "update",
            "statement_type": "update_review_state",
            "lookup": "dedupe_key",
            "expected_current_review_state": "needs_review",
            "allowed_next_states": list(ALLOWED_DECISIONS),
            "payload_column_map": dict(PAYLOAD_COLUMN_MAP),
            "required_update_columns": list(REQUIRED_UPDATE_COLUMNS),
            "required_payload_fields": [
                "dedupe_key",
                "expected_current_review_state",
                "next_review_state",
            ],
        },
        "catalog_probe_plan": catalog_probe,
        "safety_contract": _safety_contract(),
        "safe_boundaries": [
            "do_not_execute_review_state_preflight_from_api",
            "do_not_execute_review_decision_writer_from_preflight_api",
            "do_not_read_approval_token_from_review_state_preflight_api",
            "do_not_open_database_connection_from_review_state_writer_preflight",
            "do_not_commit_review_state_writer_preflight",
            "do_not_update_review_state_from_review_state_preflight",
            "do_not_update_review_state_from_review_state_writer_preflight",
            "do_not_attach_scheduler_from_review_state_writer_preflight",
            "no_remove_orphans",
            "no_momo_db_lifecycle_change",
        ],
    }
