"""Sprint 4 — engagement auto-status planned→active (AC-19)."""
from __future__ import annotations

import pathlib

from flask.testing import FlaskClient

from backend.tests.conftest import auth_headers as _h


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


def _get_engagement(client: FlaskClient, token: str, eid: int) -> dict:
    resp = client.get(f"/api/engagements/{eid}", headers=_h(token))
    assert resp.status_code == 200
    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": "Sim"},
    )
    assert resp.status_code == 201
    return resp.get_json()


def _patch_sim(client: FlaskClient, token: str, sid: int, payload: dict) -> dict:
    resp = client.patch(f"/api/simulations/{sid}", headers=_h(token), json=payload)
    assert resp.status_code == 200
    return resp.get_json()


# ---------------------------------------------------------------------------
# AC-19.1 — Auto-activate engagement on first sim in_progress
# ---------------------------------------------------------------------------


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

    eng_data = _get_engagement(client, redteam_token, eng["id"])
    assert eng_data["status"] == "planned"


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

    sim_data = _patch_sim(client, redteam_token, sim["id"], {"description": "started"})
    assert sim_data["status"] == "in_progress"

    eng_data = _get_engagement(client, redteam_token, eng["id"])
    assert eng_data["status"] == "active"


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

    _patch_sim(client, redteam_token, sim["id"], {"tactic_ids": ["TA0007"]})

    eng_data = _get_engagement(client, redteam_token, eng["id"])
    assert eng_data["status"] == "active"


# ---------------------------------------------------------------------------
# AC-19.2 — Already active → stays active (no change)
# ---------------------------------------------------------------------------


def test_patch_rt_field_does_not_change_active_engagement(
    client: FlaskClient, redteam_token: str
) -> None:
    eng = _make_engagement(client, redteam_token)
    sim = _make_sim(client, redteam_token, eng["id"])
    # First patch triggers activation.
    _patch_sim(client, redteam_token, sim["id"], {"description": "started"})

    # Second patch: engagement should remain active (no state change).
    _patch_sim(client, redteam_token, sim["id"], {"description": "updated"})

    eng_data = _get_engagement(client, redteam_token, eng["id"])
    assert eng_data["status"] == "active"


# ---------------------------------------------------------------------------
# AC-19.3 — Engagement in closed state → not touched
# ---------------------------------------------------------------------------


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

    # Manually close the engagement via API.
    close_resp = client.patch(
        f"/api/engagements/{eng['id']}",
        headers=_h(admin_token),
        json={"status": "closed"},
    )
    assert close_resp.status_code == 200

    # PATCH a sim field that would normally trigger in_progress.
    _patch_sim(client, redteam_token, sim["id"], {"description": "new work"})

    eng_data = _get_engagement(client, redteam_token, eng["id"])
    assert eng_data["status"] == "closed"


# ---------------------------------------------------------------------------
# Migration 0004 — tactic_ids column NOT NULL after upgrade
# ---------------------------------------------------------------------------


def test_migration_0004_tactic_ids_not_null_after_upgrade() -> None:
    """Alembic round-trip: tactic_ids column is NOT NULL after migration 0004."""
    import importlib

    import sqlalchemy as _sa
    from alembic.operations import Operations
    from alembic.runtime.migration import MigrationContext

    engine = _sa.create_engine("sqlite:///:memory:")

    # Create post-0003 schema (simulations with techniques column).
    with engine.begin() as conn:
        conn.execute(_sa.text(
            "CREATE TABLE simulations ("
            "  id INTEGER PRIMARY KEY,"
            "  techniques TEXT NOT NULL DEFAULT '[]'"
            ")"
        ))
        conn.execute(_sa.text(
            "INSERT INTO simulations (id, techniques) VALUES (1, '[]')"
        ))

    with engine.begin() as conn:
        ctx = MigrationContext.configure(conn, opts={"as_sql": False})
        ops = Operations(ctx)

        import alembic.op as _op_module
        _op_module._proxy = ops  # type: ignore[attr-defined]

        _mig_path = (
            pathlib.Path(__file__).parent.parent
            / "migrations" / "versions" / "0004_simulation_tactic_ids.py"
        )
        spec = importlib.util.spec_from_file_location("mig_0004", _mig_path)
        assert spec is not None and spec.loader is not None
        mig = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(mig)  # type: ignore[union-attr]
        mig.upgrade()

    insp = _sa.inspect(engine)
    cols = {c["name"]: c for c in insp.get_columns("simulations")}
    assert "tactic_ids" in cols, "tactic_ids column must exist after upgrade"
    assert cols["tactic_ids"]["nullable"] is False, "tactic_ids must be NOT NULL"

    # Existing row should have server_default applied.
    with engine.connect() as conn:
        row = conn.execute(_sa.text("SELECT tactic_ids FROM simulations WHERE id=1")).fetchone()
    assert row is not None
    import json
    assert json.loads(row[0]) == []
