"""Simulation PATCH tests: auto-transition, RBAC field-level, SOC restrictions."""
from __future__ import annotations

from flask.testing import FlaskClient

from backend.tests.conftest import auth_headers as _h


def _make_engagement(client: FlaskClient, token: str) -> dict:
    resp = client.post(
        "/api/engagements",
        headers=_h(token),
        json={"name": "Op Beta", "start_date": "2026-06-01"},
    )
    assert resp.status_code == 201
    return resp.get_json()


def _make_sim(client: FlaskClient, token: str, eid: int) -> dict:
    resp = client.post(
        f"/api/engagements/{eid}/simulations",
        headers=_h(token),
        json={"name": "Test Sim"},
    )
    assert resp.status_code == 201
    return resp.get_json()


def _patch(client: FlaskClient, token: str, sid: int, payload: dict):
    return client.patch(
        f"/api/simulations/{sid}", headers=_h(token), json=payload
    )


# ---------------------------------------------------------------------------
# Auto-transition pending → in_progress (AC-8.2)
# ---------------------------------------------------------------------------


def test_patch_triggers_auto_transition(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    assert sim["status"] == "pending"

    resp = _patch(client, redteam_token, sim["id"], {"description": "some desc"})
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "in_progress"


def test_patch_name_triggers_auto_transition(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])

    resp = _patch(client, redteam_token, sim["id"], {"name": "Updated name"})
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "in_progress"


def test_patch_commands_triggers_auto_transition(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])

    resp = _patch(client, redteam_token, sim["id"], {"commands": "cmd1\ncmd2"})
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "in_progress"


def test_patch_null_value_does_not_trigger_auto_transition(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])

    resp = _patch(client, redteam_token, sim["id"], {"description": None})
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "pending"


def test_patch_empty_string_does_not_trigger_auto_transition(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])

    resp = _patch(client, redteam_token, sim["id"], {"description": ""})
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "pending"


def test_patch_admin_triggers_auto_transition(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    sim = _make_sim(client, admin_token, eng["id"])

    resp = _patch(client, admin_token, sim["id"], {"execution_result": "success"})
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "in_progress"


def test_patch_soc_does_not_trigger_auto_transition(
    client: FlaskClient, redteam_token: str, soc_token: str
) -> None:
    """SOC patch on review_required must not trigger auto-transition."""
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])

    # Manually advance to review_required.
    client.post(
        f"/api/simulations/{sim['id']}/transition",
        headers=_h(redteam_token),
        json={"to": "review_required"},
    )

    resp = _patch(client, soc_token, sim["id"], {"soc_comment": "looks good"})
    assert resp.status_code == 200
    assert resp.get_json()["status"] == "review_required"


# ---------------------------------------------------------------------------
# Field updates
# ---------------------------------------------------------------------------


def test_patch_updates_commands_as_text(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    commands = "whoami\nnet user\nipconfig"

    resp = _patch(client, redteam_token, sim["id"], {"commands": commands})
    assert resp.status_code == 200
    assert resp.get_json()["commands"] == commands


def test_patch_updates_executed_at(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])

    resp = _patch(
        client, redteam_token, sim["id"], {"executed_at": "2026-06-01T12:00:00"}
    )
    assert resp.status_code == 200
    assert "2026-06-01" in resp.get_json()["executed_at"]


def test_patch_invalid_executed_at(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])

    resp = _patch(
        client, redteam_token, sim["id"], {"executed_at": "not-a-date"}
    )
    assert resp.status_code == 400
    assert resp.get_json()["error"] == "invalid executed_at"


def test_patch_clear_executed_at(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    _patch(client, redteam_token, sim["id"], {"executed_at": "2026-06-01T12:00:00"})

    resp = _patch(client, redteam_token, sim["id"], {"executed_at": None})
    assert resp.status_code == 200
    assert resp.get_json()["executed_at"] is None


# ---------------------------------------------------------------------------
# SOC RBAC field-level (AC-9.1, AC-9.2)
# ---------------------------------------------------------------------------


def test_soc_cannot_patch_before_review_required(
    client: FlaskClient, redteam_token: str, soc_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    assert sim["status"] == "pending"

    resp = _patch(client, soc_token, sim["id"], {"soc_comment": "hello"})
    assert resp.status_code == 403
    assert "not ready" in resp.get_json()["error"]


def test_soc_cannot_patch_in_progress(
    client: FlaskClient, redteam_token: str, soc_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    _patch(client, redteam_token, sim["id"], {"description": "in progress now"})

    resp = _patch(client, soc_token, sim["id"], {"soc_comment": "hello"})
    assert resp.status_code == 403


def test_soc_can_patch_when_review_required(
    client: FlaskClient, redteam_token: str, soc_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    client.post(
        f"/api/simulations/{sim['id']}/transition",
        headers=_h(redteam_token),
        json={"to": "review_required"},
    )

    resp = _patch(
        client,
        soc_token,
        sim["id"],
        {"soc_comment": "Detected", "log_source": "SIEM", "incident_number": "INC-001"},
    )
    assert resp.status_code == 200
    body = resp.get_json()
    assert body["soc_comment"] == "Detected"
    assert body["log_source"] == "SIEM"
    assert body["incident_number"] == "INC-001"


def test_patch_when_done_returns_409(
    client: FlaskClient, redteam_token: str, soc_token: str
) -> None:
    """Done is terminal — PATCH is rejected for ALL roles (AC-18.1)."""
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    client.post(
        f"/api/simulations/{sim['id']}/transition",
        headers=_h(redteam_token),
        json={"to": "review_required"},
    )
    client.post(
        f"/api/simulations/{sim['id']}/transition",
        headers=_h(soc_token),
        json={"to": "done"},
    )

    resp = _patch(client, soc_token, sim["id"], {"soc_comment": "Final note"})
    assert resp.status_code == 409
    assert resp.get_json()["error"] == "simulation is done — reopen first"


def test_soc_cannot_edit_redteam_fields(
    client: FlaskClient, redteam_token: str, soc_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    client.post(
        f"/api/simulations/{sim['id']}/transition",
        headers=_h(redteam_token),
        json={"to": "review_required"},
    )

    resp = _patch(client, soc_token, sim["id"], {"description": "redteam field"})
    assert resp.status_code == 403
    assert resp.get_json()["error"] == "soc cannot edit redteam fields"


def test_patch_simulation_404(client: FlaskClient, redteam_token: str) -> None:
    resp = _patch(client, redteam_token, 9999, {"name": "x"})
    assert resp.status_code == 404


def test_invalid_executed_at_does_not_mutate_other_fields(
    client: FlaskClient, redteam_token: str
) -> None:
    """invalid executed_at must return 400 without persisting other fields in the payload."""
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    original_description = sim["description"]

    resp = _patch(
        client,
        redteam_token,
        sim["id"],
        {"description": "should-not-stick", "executed_at": "not-a-date"},
    )
    assert resp.status_code == 400

    get_resp = client.get(
        f"/api/simulations/{sim['id']}",
        headers={"Authorization": f"Bearer {redteam_token}"},
    )
    assert get_resp.status_code == 200
    assert get_resp.get_json()["description"] == original_description
