"""SimulationTemplate CRUD: list, create, get, patch, delete + RBAC + dedup."""
from __future__ import annotations

from flask.testing import FlaskClient

from backend.app.extensions import db
from backend.app.models import User
from backend.app.models.simulation_template import SimulationTemplate
from backend.tests.conftest import auth_headers as _h  # noqa: E402

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


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


# ---------------------------------------------------------------------------
# List
# ---------------------------------------------------------------------------


def test_list_templates_empty(client: FlaskClient, admin_token: str) -> None:
    resp = client.get("/api/templates", headers=_h(admin_token))
    assert resp.status_code == 200
    assert resp.get_json() == []


def test_list_templates_soc_forbidden(client: FlaskClient, soc_token: str) -> None:
    resp = client.get("/api/templates", headers=_h(soc_token))
    assert resp.status_code == 403


def test_list_templates_unauthenticated(client: FlaskClient) -> None:
    resp = client.get("/api/templates")
    assert resp.status_code == 401


# ---------------------------------------------------------------------------
# Create
# ---------------------------------------------------------------------------


def test_create_template_as_admin(
    client: FlaskClient, admin_user: User, admin_token: str
) -> None:
    body = _make_template(
        client,
        admin_token,
        description="desc",
        commands="cmd",
        prerequisites="prereq",
    )
    assert body["name"] == "Template Alpha"
    assert body["description"] == "desc"
    assert body["commands"] == "cmd"
    assert body["prerequisites"] == "prereq"
    assert body["techniques"] == []
    assert body["tactics"] == []
    assert body["created_by"] == {"id": admin_user.id, "username": "admin1"}
    assert body["id"] is not None


def test_create_template_as_redteam(
    client: FlaskClient, redteam_user: User, redteam_token: str
) -> None:
    body = _make_template(client, redteam_token)
    assert body["created_by"]["username"] == "redteam1"


def test_create_template_soc_forbidden(client: FlaskClient, soc_token: str) -> None:
    resp = client.post(
        "/api/templates", headers=_h(soc_token), json={"name": "T"}
    )
    assert resp.status_code == 403


def test_create_template_missing_name(client: FlaskClient, admin_token: str) -> None:
    resp = client.post("/api/templates", headers=_h(admin_token), json={})
    assert resp.status_code == 400
    assert "name" in resp.get_json()["error"]


def test_create_template_duplicate_name_409(
    client: FlaskClient, admin_token: str
) -> None:
    _make_template(client, admin_token)
    resp = client.post(
        "/api/templates", headers=_h(admin_token), json={"name": "Template Alpha"}
    )
    assert resp.status_code == 409
    assert "already exists" in resp.get_json()["error"]


def test_create_template_unknown_technique_id_400(
    client: FlaskClient, admin_token: str
) -> None:
    resp = client.post(
        "/api/templates",
        headers=_h(admin_token),
        json={"name": "T", "technique_ids": ["T9999.999"]},
    )
    assert resp.status_code == 400
    assert "unknown technique id" in resp.get_json()["error"]


def test_create_template_unknown_tactic_id_400(
    client: FlaskClient, admin_token: str
) -> None:
    resp = client.post(
        "/api/templates",
        headers=_h(admin_token),
        json={"name": "T", "tactic_ids": ["TA9999"]},
    )
    assert resp.status_code == 400
    assert "unknown tactic id" in resp.get_json()["error"]


# ---------------------------------------------------------------------------
# Get single
# ---------------------------------------------------------------------------


def test_get_template(client: FlaskClient, admin_token: str) -> None:
    created = _make_template(client, admin_token)
    resp = client.get(f"/api/templates/{created['id']}", headers=_h(admin_token))
    assert resp.status_code == 200
    assert resp.get_json()["id"] == created["id"]


def test_get_template_not_found(client: FlaskClient, admin_token: str) -> None:
    resp = client.get("/api/templates/9999", headers=_h(admin_token))
    assert resp.status_code == 404


def test_get_template_soc_forbidden(
    client: FlaskClient, admin_token: str, soc_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.get(f"/api/templates/{created['id']}", headers=_h(soc_token))
    assert resp.status_code == 403


# ---------------------------------------------------------------------------
# Patch
# ---------------------------------------------------------------------------


def test_patch_template_name(client: FlaskClient, admin_token: str) -> None:
    created = _make_template(client, admin_token)
    resp = client.patch(
        f"/api/templates/{created['id']}",
        headers=_h(admin_token),
        json={"name": "Renamed"},
    )
    assert resp.status_code == 200
    assert resp.get_json()["name"] == "Renamed"
    assert resp.get_json()["updated_at"] is not None


def test_patch_template_empty_name_rejected(
    client: FlaskClient, admin_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.patch(
        f"/api/templates/{created['id']}",
        headers=_h(admin_token),
        json={"name": ""},
    )
    assert resp.status_code == 400


def test_patch_template_unknown_field_rejected(
    client: FlaskClient, admin_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.patch(
        f"/api/templates/{created['id']}",
        headers=_h(admin_token),
        json={"bogus_field": "x"},
    )
    assert resp.status_code == 400
    assert "unknown fields" in resp.get_json()["error"]


def test_patch_template_duplicate_name_409(
    client: FlaskClient, admin_token: str
) -> None:
    _make_template(client, admin_token, name="T1")
    t2 = _make_template(client, admin_token, name="T2")
    resp = client.patch(
        f"/api/templates/{t2['id']}",
        headers=_h(admin_token),
        json={"name": "T1"},
    )
    assert resp.status_code == 409


def test_patch_template_soc_forbidden(
    client: FlaskClient, admin_token: str, soc_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.patch(
        f"/api/templates/{created['id']}",
        headers=_h(soc_token),
        json={"name": "X"},
    )
    assert resp.status_code == 403


def test_patch_template_not_found(client: FlaskClient, admin_token: str) -> None:
    resp = client.patch(
        "/api/templates/9999", headers=_h(admin_token), json={"name": "X"}
    )
    assert resp.status_code == 404


def test_patch_template_unknown_technique_id_400(
    client: FlaskClient, admin_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.patch(
        f"/api/templates/{created['id']}",
        headers=_h(admin_token),
        json={"technique_ids": ["T9999.999"]},
    )
    assert resp.status_code == 400
    assert "unknown technique id" in resp.get_json()["error"]


def test_patch_template_unknown_tactic_id_400(
    client: FlaskClient, admin_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.patch(
        f"/api/templates/{created['id']}",
        headers=_h(admin_token),
        json={"tactic_ids": ["TA9999"]},
    )
    assert resp.status_code == 400
    assert "unknown tactic id" in resp.get_json()["error"]


# ---------------------------------------------------------------------------
# Delete
# ---------------------------------------------------------------------------


def test_delete_template(
    client: FlaskClient, app, admin_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.delete(f"/api/templates/{created['id']}", headers=_h(admin_token))
    assert resp.status_code == 204
    with app.app_context():
        assert db.session.get(SimulationTemplate, created["id"]) is None


def test_delete_template_not_found(client: FlaskClient, admin_token: str) -> None:
    resp = client.delete("/api/templates/9999", headers=_h(admin_token))
    assert resp.status_code == 404


def test_delete_template_soc_forbidden(
    client: FlaskClient, admin_token: str, soc_token: str
) -> None:
    created = _make_template(client, admin_token)
    resp = client.delete(f"/api/templates/{created['id']}", headers=_h(soc_token))
    assert resp.status_code == 403


# ---------------------------------------------------------------------------
# List returns ordered by name
# ---------------------------------------------------------------------------


def test_list_templates_ordered_by_name(
    client: FlaskClient, admin_token: str
) -> None:
    for name in ("Zebra", "Alpha", "Midpoint"):
        _make_template(client, admin_token, name=name)
    body = client.get("/api/templates", headers=_h(admin_token)).get_json()
    names = [t["name"] for t in body]
    assert names == sorted(names)
