import json
import subprocess
import sys
from pathlib import Path

import pytest
from flask import Blueprint, Flask, jsonify


ROOT = Path(__file__).resolve().parents[1]


def test_security_governance_review_is_honest_and_release_gate_passes():
    from services.security_governance_review_service import (
        build_security_governance_review,
    )

    report = build_security_governance_review(root=ROOT, detail_limit=12)

    assert report["status"] == "partial"
    assert report["release_gate"]["status"] == "pass"
    assert report["release_gate"]["blocking_failure_count"] == 0
    assert report["completion"]["asset_field_coverage_percent"] == 100.0
    assert report["completion"]["asset_runtime_reconciliation_complete"] is False
    assert report["completion"]["runtime_closure_passed"] < report["completion"]["runtime_closure_total"]
    assert report["work_items"][0]["id"] == "SEC-P0-001"
    assert report["work_items"][1]["id"] == "SEC-P0-002"
    assert report["work_items"][1]["status"] == "in_progress"
    assert len(report["work_items"]) == 22
    assert report["work_items"][9]["id"] == "REL-P0-001"
    assert any(item["id"] == "QA-P1-001" for item in report["work_items"])
    assert report["work_items"][-1]["id"] == "CHAOS-P2-001"
    checks = {item["id"]: item for item in report["checks"]}
    assert checks["PR.AC-database-identity-rbac"]["status"] == "partial"
    identity_markers = checks["PR.AC-database-identity-rbac"]["evidence"]["source"]["markers"]
    assert identity_markers["identity_mutation_audit"] is True
    assert identity_markers["permission_mutation_audit"] is True
    access = checks["PR.AA-route-access-control"]["evidence"]
    assert access["mutating_unguarded_count"] == 0
    assert access["unclassified_unguarded_count"] == 0
    assert access["unguarded_count"] == 8
    dependencies = checks["ID.RA-dependency-vulnerability-management"]["evidence"]
    assert dependencies["sbom_files"] == []
    supply = checks["GV.SC-gitea-only-supply-chain"]
    assert supply["status"] == "pass"
    assert supply["evidence"]["docker_build_context"]["safe"] is True
    quality = checks["GV.QA-test-governance"]
    assert quality["status"] == "partial"
    assert quality["evidence"]["collection_error_count"] == 1
    assert quality["evidence"]["full_suite_passed"] == 1701
    assert report["safety"] == {
        "read_only": True,
        "network_calls": False,
        "database_reads": False,
        "database_writes": False,
        "secret_reads": False,
        "external_side_effects": False,
    }


def test_security_governance_cli_strict_mode_passes_source_gate():
    completed = subprocess.run(
        [
            sys.executable,
            str(ROOT / "scripts" / "ops" / "report_security_governance_review.py"),
            "--root",
            str(ROOT),
            "--compact",
            "--strict",
        ],
        cwd=ROOT,
        capture_output=True,
        text=True,
        timeout=30,
        check=False,
    )

    assert completed.returncode == 0, completed.stderr or completed.stdout
    payload = json.loads(completed.stdout)
    assert payload["release_gate"]["status"] == "pass"
    assert payload["status"] == "partial"


def test_security_governance_cli_can_emit_commit_bound_source_receipt():
    source_commit = "a" * 40
    completed = subprocess.run(
        [
            sys.executable,
            str(ROOT / "scripts" / "ops" / "report_security_governance_review.py"),
            "--root",
            str(ROOT),
            "--strict",
            "--source-commit",
            source_commit,
        ],
        cwd=ROOT,
        capture_output=True,
        text=True,
        timeout=30,
        check=False,
    )

    assert completed.returncode == 0, completed.stderr or completed.stdout
    payload = json.loads(completed.stdout)
    assert payload["receipt_kind"] == "security_governance_source_receipt"
    assert payload["source_commit"] == source_commit
    assert payload["release_gate"]["status"] == "pass"


def test_runtime_subset_uses_commit_bound_source_receipt(tmp_path):
    from services.security_governance_review_service import (
        build_security_governance_review,
    )

    source_report = build_security_governance_review(root=ROOT)
    source_report["receipt_kind"] = "security_governance_source_receipt"
    source_report["source_commit"] = "b" * 40
    governance = tmp_path / "governance"
    governance.mkdir()
    (governance / "security_governance_source_receipt.json").write_text(
        json.dumps(source_report, ensure_ascii=False),
        encoding="utf-8",
    )

    runtime_report = build_security_governance_review(root=tmp_path)
    checks = {item["id"]: item for item in runtime_report["checks"]}

    supply = checks["GV.SC-gitea-only-supply-chain"]
    policy = checks["GV.PO-policy-alignment"]
    assert supply["status"] == "pass"
    assert supply["evidence"]["evidence_source"] == "cd_source_receipt"
    assert policy["status"] == "pass"
    assert policy["evidence"]["evidence_source"] == "cd_source_receipt"


def test_runtime_receipt_advances_access_readback_without_hiding_remaining_debt(tmp_path):
    from services.security_governance_review_service import (
        _runtime_receipt,
    )

    governance = tmp_path / "governance"
    governance.mkdir()
    payload = {
        "receipt_kind": "security_governance_runtime_receipt",
        "trace_id": "secgov-test",
        "run_id": "secgov-test-run",
        "work_item_id": "SEC-P0-001",
        "source_commit": "c" * 40,
        "deployed_version": "V10.773",
        "deployment_mode": "controlled_manual_fallback",
        "access_control": {
            "status": "pass",
            "sensitive_routes_denied": True,
            "security_headers_passed": True,
            "secure_cookie": True,
            "sensitive_route_count": 8,
            "denied_route_count": 8,
            "metrics_public": False,
            "metrics_internal_canary": True,
            "metrics_target_health": "up",
        },
        "database": {"unchanged": True},
        "post_verifier": {
            "health_status": "healthy",
            "governance_gate": "pass",
        },
        "cd": {
            "run_id": 1095,
            "status": "waiting",
            "blocker": "no_ewoooc_host_runner",
        },
        "learning_recorded": False,
        "next_machine_action": "inventory_active_users_then_shadow_database_auth_before_cutover",
    }
    (governance / "security_governance_runtime_receipt.json").write_text(
        json.dumps(payload, ensure_ascii=False),
        encoding="utf-8",
    )

    receipt = _runtime_receipt(tmp_path)

    assert receipt["security_readback_passed"] is True
    assert receipt["database_unchanged"] is True
    assert receipt["metrics_public"] is False
    assert receipt["metrics_internal_canary"] is True
    assert receipt["metrics_target_health"] == "up"
    assert receipt["cd_status"] == "waiting"
    assert receipt["learning_recorded"] is False


def test_runtime_receipt_rejects_public_metrics_even_when_other_checks_pass(tmp_path):
    from services.security_governance_review_service import _runtime_receipt

    governance = tmp_path / "governance"
    governance.mkdir()
    payload = {
        "receipt_kind": "security_governance_runtime_receipt",
        "source_commit": "d" * 40,
        "access_control": {
            "status": "pass",
            "sensitive_routes_denied": True,
            "security_headers_passed": True,
            "secure_cookie": True,
            "metrics_public": True,
            "metrics_internal_canary": True,
        },
        "database": {"unchanged": True},
        "post_verifier": {"health_status": "healthy", "governance_gate": "pass"},
    }
    (governance / "security_governance_runtime_receipt.json").write_text(
        json.dumps(payload),
        encoding="utf-8",
    )

    receipt = _runtime_receipt(tmp_path)

    assert receipt["security_readback_passed"] is False


def test_auth_identity_runtime_receipt_requires_database_only_closure(tmp_path):
    from services.security_governance_review_service import (
        _auth_identity_runtime_receipt,
    )

    governance = tmp_path / "governance"
    governance.mkdir()
    path = governance / "auth_identity_runtime_receipt.json"
    payload = {
        "receipt_kind": "auth_identity_runtime_readback",
        "work_item_id": "SEC-P0-002",
        "status": "canary_ready",
        "auth_identity_mode": "hybrid",
        "canary_ready": True,
        "runtime_closed": False,
        "inventory": {"users_total": 2, "active_admin_count": 2},
        "checks": {"legacy_shared_password_retired": False},
    }
    path.write_text(json.dumps(payload), encoding="utf-8")
    canary = _auth_identity_runtime_receipt(tmp_path)
    assert canary["canary_ready"] is True
    assert canary["runtime_closed"] is False

    payload.update({
        "status": "completed",
        "auth_identity_mode": "database",
        "runtime_closed": True,
        "checks": {"legacy_shared_password_retired": True},
    })
    path.write_text(json.dumps(payload), encoding="utf-8")
    closed = _auth_identity_runtime_receipt(tmp_path)
    assert closed["runtime_closed"] is True
    assert closed["legacy_shared_password_retired"] is True


def test_sensitive_blueprint_policy_denies_anonymous_api(monkeypatch):
    import auth
    from services.http_access_policy_service import enforce_http_access_policy

    monkeypatch.setattr(auth, "DISABLE_LOGIN", False)
    app = Flask(__name__)
    app.secret_key = "test"
    vendor = Blueprint("vendor", __name__)

    @vendor.route("/vendor-stockout/api/vendor/list")
    def list_vendors():
        return jsonify({"success": True})

    app.register_blueprint(vendor)
    app.before_request(enforce_http_access_policy)
    client = app.test_client()

    anonymous = client.get("/vendor-stockout/api/vendor/list")
    assert anonymous.status_code == 401
    assert anonymous.get_json()["error"] == "authentication_required"

    with client.session_transaction() as session:
        session["logged_in"] = True
        session["role"] = "admin"
        session["username"] = "legacy-admin"
        session["auth_source"] = "legacy_shared_password"
    authenticated = client.get("/vendor-stockout/api/vendor/list")
    assert authenticated.status_code == 200


def test_public_url_validator_blocks_ssrf_targets(monkeypatch):
    from utils import security

    monkeypatch.setattr(
        security.socket,
        "getaddrinfo",
        lambda *_args, **_kwargs: [(2, 1, 6, "", ("93.184.216.34", 443))],
    )
    assert security.validate_public_http_url("https://example.com/path") == "https://example.com/path"

    monkeypatch.setattr(
        security.socket,
        "getaddrinfo",
        lambda *_args, **_kwargs: [(2, 1, 6, "", ("127.0.0.1", 80))],
    )
    with pytest.raises(ValueError, match="loopback"):
        security.validate_public_http_url("http://example.com/")


def test_alert_auto_fix_protects_database_and_denies_unknown_container(monkeypatch):
    from routes import alert_routes

    def fail_if_called(*_args, **_kwargs):
        raise AssertionError("subprocess must not run for denied containers")

    monkeypatch.setattr(alert_routes.subprocess, "run", fail_if_called)

    protected = alert_routes.auto_fix_container("momo-db", "memory")
    assert protected["success"] is False
    assert "受保護" in protected["message"]

    unknown = alert_routes.auto_fix_container("stock-platform", "memory")
    assert unknown["success"] is False
    assert "白名單" in unknown["message"]


def test_security_governance_smoke_family_is_warning_for_nonblocking_debt(monkeypatch):
    from services import ai_automation_smoke_service as smoke
    from services import security_governance_review_service as review_service

    monkeypatch.setattr(
        review_service,
        "build_security_governance_review",
        lambda **_kwargs: {
            "policy": "test-policy",
            "status": "partial",
            "completion": {
                "program_percent": 45.0,
                "asset_field_coverage_percent": 100.0,
                "asset_runtime_reconciliation_complete": False,
                "runtime_closure_passed": 0,
                "runtime_closure_total": 4,
            },
            "release_gate": {"blocking_failure_count": 0},
            "next_machine_action": "close_access_control",
        },
    )

    check = smoke._security_governance_review_check()

    assert check["status"] == "warning"
    assert check["details"]["blocking_failure_count"] == 0
    assert check["details"]["next_machine_action"] == "close_access_control"
