"""Check that qaApprover != prodApprover for a contract (ARCHITECTURE.md
§10.3, D-042). Reads `approver_qa` from the DynamoDB outbox for the
contractId, compares to the prod-dispatch `gitea.actor` / `github.actor`.
Blocks on equality, emits `SEPARATION_OF_DUTIES_VIOLATION`, routes a halt
artifact to SRE on-call.

v1.9 (REQ-107, D-085): route_halt_artifact is a real implementation —
publishes to SNS topic `acdl-sod-halt` (ARN from ACDL_SOD_HALT_TOPIC_ARN)
when set; falls back to a structured stderr emission + a
SEPARATION_OF_DUTIES_VIOLATION event write to the DynamoDB outbox when
unset. No silent print-only stub.
"""

import os
import sys
from typing import Optional, Tuple


def check(outbox_client, contract_id: str,
          current_prod_approver: Optional[str]) -> Tuple[bool, str]:
    """Return (ok, reason). ok=False means block the prod promotion."""
    if outbox_client is None:
        return (True, "no outbox client (dev-only spike)")
    item = outbox_client.get(contract_id)
    if item is None:
        return (True, "no prior approver (first promotion)")
    qa_approver = item.get("approver_qa")
    if not qa_approver:
        return (True, "no QA approver recorded (dev-only spike)")
    if current_prod_approver is None:
        return (True, "no prod approver supplied (dev-only spike)")
    if qa_approver == current_prod_approver:
        return (False,
                f"SEPARATION_OF_DUTIES_VIOLATION: "
                f"qaApprover==prodApprover=={qa_approver}")
    return (True, "distinct")


def route_halt_artifact(contract_id: str, violation_reason: str,
                        oncall_client=None) -> None:
    """Route a halt artifact to SRE on-call (REQ-107, D-085).

    When ACDL_SOD_HALT_TOPIC_ARN is set, publish to the SNS topic via
    boto3. When unset (dev/CI), fall back to a structured stderr emission
    + a SEPARATION_OF_DUTIES_VIOLATION event write to the DynamoDB outbox
    via outbox_writer.write_event (so the halt is in the audit chain).
    The oncall_client, when provided, is the SNS client (test injection).
    """
    topic_arn = os.environ.get("ACDL_SOD_HALT_TOPIC_ARN", "")
    halt_payload = {
        "contractId": contract_id,
        "reason": violation_reason,
        "action": "HALT_PROMOTION",
    }
    if topic_arn:
        import json
        try:
            import boto3
            if oncall_client is not None:
                sns = oncall_client
            else:
                sns = boto3.client("sns")
            sns.publish(
                TopicArn=topic_arn,
                Message=json.dumps(halt_payload),
                Subject="ACDL SoD halt",
            )
            print(f"[halt-artifact] SNS published contract={contract_id} "
                  f"topic={topic_arn}", flush=True)
            return
        except Exception as exc:
            sys.stderr.write(
                f"[halt-artifact] SNS publish failed ({exc}); "
                f"falling back to outbox event\n"
            )
    # Fallback: stderr + outbox event (the halt is in the audit chain).
    sys.stderr.write(
        f"[halt-artifact] contract={contract_id} reason={violation_reason} "
        f"oncall={oncall_client} (no SNS topic — outbox fallback)\n"
    )
    try:
        from core.outbox_writer import write_event
        write_event({
            "contractId": contract_id,
            "eventType": "SEPARATION_OF_DUTIES_VIOLATION",
            "environment": "",
            "stack": "",
            "score": 0,
            "band": "halt",
            "reason": violation_reason,
        })
    except Exception as exc:
        sys.stderr.write(
            f"[halt-artifact] outbox fallback write failed ({exc})\n"
        )