"""Tests for creating simulations from a template (POST /api/engagements/<eid>/simulations)."""
from __future__ import annotations

from pathlib import Path

from alembic.operations import Operations
from alembic.runtime.migration import MigrationContext
from flask.testing import FlaskClient
from sqlalchemy import create_engine, text

from backend.tests.conftest import auth_headers as _h

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


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


def _make_template(client: FlaskClient, token: str, **kw) -> dict:
    payload = {"name": "Base Template", **kw}
    resp = client.post("/api/templates", headers=_h(token), json=payload)
    assert resp.status_code == 201, resp.get_json()
    return resp.get_json()


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


# ---------------------------------------------------------------------------
# Instantiation
# ---------------------------------------------------------------------------


def test_create_simulation_from_template_copies_fields(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    tmpl = _make_template(
        client,
        admin_token,
        description="template desc",
        commands="template cmd",
        prerequisites="template prereq",
    )
    sim = _make_sim(client, admin_token, eng["id"], template_id=tmpl["id"])

    assert sim["description"] == "template desc"
    assert sim["commands"] == "template cmd"
    assert sim["prerequisites"] == "template prereq"
    assert sim["techniques"] == []
    assert sim["tactics"] == []
    assert sim["status"] == "pending"


def test_create_simulation_name_overrides_template(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    tmpl = _make_template(client, admin_token)
    sim = _make_sim(
        client, admin_token, eng["id"], name="Custom Name", template_id=tmpl["id"]
    )
    assert sim["name"] == "Custom Name"


def test_create_simulation_name_falls_back_to_template_name(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    tmpl = _make_template(client, admin_token, name="Recon Template")
    resp = client.post(
        f"/api/engagements/{eng['id']}/simulations",
        headers=_h(admin_token),
        json={"template_id": tmpl["id"]},
    )
    assert resp.status_code == 201
    assert resp.get_json()["name"] == "Recon Template"


def test_create_simulation_template_not_found(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    resp = client.post(
        f"/api/engagements/{eng['id']}/simulations",
        headers=_h(admin_token),
        json={"name": "S", "template_id": 9999},
    )
    assert resp.status_code == 404
    assert "Template not found" in resp.get_json()["error"]


def test_create_simulation_without_template_unaffected(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    sim = _make_sim(client, admin_token, eng["id"])
    assert sim["description"] is None
    assert sim["commands"] is None
    assert sim["prerequisites"] is None


def test_create_simulation_from_template_status_is_pending(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    tmpl = _make_template(client, admin_token)
    sim = _make_sim(client, admin_token, eng["id"], template_id=tmpl["id"])
    assert sim["status"] == "pending"


def test_delete_template_does_not_cascade_to_simulations(
    client: FlaskClient, admin_token: str
) -> None:
    eng = _make_engagement(client, admin_token)
    tmpl = _make_template(client, admin_token)
    sim = _make_sim(client, admin_token, eng["id"], template_id=tmpl["id"])
    sid = sim["id"]

    # Delete the template.
    del_resp = client.delete(
        f"/api/templates/{tmpl['id']}", headers=_h(admin_token)
    )
    assert del_resp.status_code == 204

    # Simulation must still be retrievable.
    get_resp = client.get(f"/api/simulations/{sid}", headers=_h(admin_token))
    assert get_resp.status_code == 200
    assert get_resp.get_json()["id"] == sid


# ---------------------------------------------------------------------------
# Migration round-trip
# ---------------------------------------------------------------------------


def test_migration_0005_round_trip() -> None:
    engine = create_engine("sqlite:///:memory:")
    migration_file = (
        Path(__file__).parent.parent
        / "migrations"
        / "versions"
        / "0005_simulation_templates.py"
    )

    import importlib.util

    spec = importlib.util.spec_from_file_location("m0005", migration_file)
    assert spec is not None
    module = importlib.util.module_from_spec(spec)
    assert spec.loader is not None
    spec.loader.exec_module(module)  # type: ignore[union-attr]

    with engine.begin() as conn:
        ctx = MigrationContext.configure(conn)
        import alembic.op as op_module

        op_module._proxy = Operations(ctx)  # type: ignore[attr-defined]

        # Create users table (FK dependency).
        conn.execute(
            text(
                "CREATE TABLE users ("
                "id INTEGER PRIMARY KEY, "
                "username TEXT NOT NULL, "
                "password_hash TEXT NOT NULL, "
                "role TEXT NOT NULL DEFAULT 'redteam', "
                "created_at DATETIME"
                ")"
            )
        )

        module.upgrade()

        tables_after = conn.execute(
            text("SELECT name FROM sqlite_master WHERE type='table'")
        ).fetchall()
        table_names = {r[0] for r in tables_after}
        assert "simulation_templates" in table_names

        cols = conn.execute(
            text("PRAGMA table_info(simulation_templates)")
        ).fetchall()
        col_names = {c[1] for c in cols}
        for expected in ("id", "name", "techniques", "tactic_ids", "created_by_id"):
            assert expected in col_names, f"missing column: {expected}"

        module.downgrade()

        tables_after_down = conn.execute(
            text("SELECT name FROM sqlite_master WHERE type='table'")
        ).fetchall()
        table_names_down = {r[0] for r in tables_after_down}
        assert "simulation_templates" not in table_names_down
