"""Fixtures : base PostgreSQL de test recréée à chaque session, clients authentifiés par rôle."""
from __future__ import annotations

import importlib.util
import os
import tempfile
from collections.abc import Iterator
from pathlib import Path
from typing import Any

import pyotp
import pytest

ROOT = Path(__file__).resolve().parent.parent
_tmp = tempfile.mkdtemp(prefix="edcf-test-")

os.environ.setdefault("EDCF_ENV_FILE", str(Path("~/.edcf-dev.env").expanduser()))
from edcf.core import config as _config  # noqa: E402

_config._load_env_file(os.environ["EDCF_ENV_FILE"])
os.environ["EDCF_ENV"] = "test"
os.environ["EDCF_DATABASE_URL"] = os.environ["EDCF_TEST_DATABASE_URL"]
os.environ["EDCF_DATABASE_OWNER_URL"] = os.environ["EDCF_TEST_DATABASE_URL"]
os.environ["EDCF_STORAGE_DIR"] = _tmp
os.environ["EDCF_PUBLIC_ORIGIN"] = "http://testserver"
os.environ["EDCF_COOKIE_SECURE"] = "false"
os.environ["EDCF_AV_MODE"] = "optional"
os.environ.pop("EDCF_CLAMAV_SOCKET", None)
_config.get_config.cache_clear()

from fastapi.testclient import TestClient  # noqa: E402
from sqlalchemy import create_engine, text  # noqa: E402

from edcf.core import db as dbmod  # noqa: E402
from edcf.core import security  # noqa: E402
from edcf.models import User  # noqa: E402

PASSWORD = "Correct-Horse-Battery-52"
SEED_TABLES_KEPT = {"roles", "permissions", "role_permissions", "indicator_categories", "alembic_version"}


def _migration_module() -> Any:
    path = next((ROOT / "alembic" / "versions").glob("0002_*.py"))
    spec = importlib.util.spec_from_file_location("mig0002", path)
    assert spec and spec.loader
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


@pytest.fixture(scope="session", autouse=True)
def database() -> Iterator[None]:
    url = os.environ["EDCF_TEST_DATABASE_URL"]
    eng = create_engine(url)
    with eng.begin() as conn:
        conn.execute(text("DROP SCHEMA public CASCADE; CREATE SCHEMA public;"))
    from alembic import command
    from alembic.config import Config
    cfg = Config(str(ROOT / "alembic.ini"))
    cfg.set_main_option("script_location", str(ROOT / "alembic"))
    cfg.attributes["url"] = url
    command.upgrade(cfg, "head")
    eng.dispose()
    dbmod.configure(url)
    yield


@pytest.fixture(autouse=True)
def clean_tables() -> Iterator[None]:
    yield
    mig = _migration_module()
    with dbmod.engine().begin() as conn:
        tables = [r[0] for r in conn.execute(text(
            "SELECT tablename FROM pg_tables WHERE schemaname='public'"))]
        to_truncate = [t for t in tables if t not in SEED_TABLES_KEPT]
        conn.execute(text("TRUNCATE " + ", ".join(to_truncate) + " RESTART IDENTITY CASCADE"))
        for pos, (code, label, kind, unit, aliases) in enumerate(mig.INDICATORS, start=1):
            conn.execute(text(
                "INSERT INTO indicators (code,label,position,is_active,category_id,detail_kind,detail_unit,"
                "include_in_total,aliases) VALUES (:c,:l,:p,true,1,:k,:u,true,CAST(:a AS jsonb))"),
                {"c": code, "l": label, "p": pos, "k": kind, "u": unit, "a": __import__("json").dumps(aliases)})
    from edcf.documents import render
    render._cache.clear()


@pytest.fixture
def app() -> Any:
    from edcf.main import create_app
    return create_app()


def make_user(username: str, role: str = "admin", *, mfa: bool | None = None, can_export: bool = False,
              must_change: bool = False, password: str = PASSWORD) -> dict[str, Any]:
    secret = security.new_totp_secret()
    mfa = (role == "admin") if mfa is None else mfa
    with dbmod.session_scope() as db:
        u = User(username=username, display_name=username.title(), role_code=role, is_active=True,
                 can_export=can_export, password_hash=security.hash_password(password),
                 must_change_password=must_change, mfa_enabled=mfa,
                 mfa_secret_enc=security.encrypt_secret(secret) if mfa else None)
        db.add(u)
        db.flush()
        uid = u.id
    return {"id": uid, "username": username, "password": password, "secret": secret if mfa else None}


class Api:
    """Client de test qui gère le cookie de session et le jeton CSRF comme le ferait l'interface."""

    def __init__(self, app: Any):
        self.c = TestClient(app, base_url="http://testserver", headers={"Origin": "http://testserver"})
        self.csrf = ""

    def _h(self, extra: dict[str, str] | None = None) -> dict[str, str]:
        h = {"X-CSRF-Token": self.csrf} if self.csrf else {}
        h.update(extra or {})
        return h

    def get(self, url: str, **kw: Any) -> Any:
        return self.c.get(url, **kw)

    def post(self, url: str, json: Any = None, **kw: Any) -> Any:
        r = self.c.post(url, json=json, headers=self._h(kw.pop("headers", None)), **kw)
        self._capture(r)
        return r

    def put(self, url: str, json: Any = None, **kw: Any) -> Any:
        return self.c.put(url, json=json, headers=self._h(kw.pop("headers", None)), **kw)

    def patch(self, url: str, json: Any = None, **kw: Any) -> Any:
        return self.c.patch(url, json=json, headers=self._h(kw.pop("headers", None)), **kw)

    def delete(self, url: str, **kw: Any) -> Any:
        return self.c.delete(url, headers=self._h(kw.pop("headers", None)), **kw)

    def _capture(self, r: Any) -> None:
        try:
            body = r.json()
        except Exception:
            return
        if isinstance(body, dict):
            if "csrf_token" in body:
                self.csrf = body["csrf_token"]
            elif isinstance(body.get("me"), dict) and "csrf_token" in body["me"]:
                self.csrf = body["me"]["csrf_token"]

    def login(self, user: dict[str, Any]) -> Any:
        r = self.post("/api/auth/login", {"username": user["username"], "password": user["password"]})
        assert r.status_code == 200, r.text
        if r.json()["state"]["mfa_pending"]:
            r = self.post("/api/auth/mfa", {"code": pyotp.TOTP(user["secret"]).now()})
            assert r.status_code == 200, r.text
        return r


@pytest.fixture
def api(app: Any) -> Api:
    return Api(app)


@pytest.fixture
def admin(app: Any) -> Api:
    a = Api(app)
    a.user = make_user("gestionnaire")  # type: ignore[attr-defined]
    a.login(a.user)  # type: ignore[attr-defined]
    return a


@pytest.fixture
def reader(app: Any) -> Api:
    a = Api(app)
    a.user = make_user("lecteur", "reader")  # type: ignore[attr-defined]
    a.login(a.user)  # type: ignore[attr-defined]
    return a


def full_rows(api: Api, y: int = 2026, w: int = 40, values: dict[int, Any] | None = None) -> list[dict[str, Any]]:
    data = api.get(f"/api/bilans/{y}/{w}").json()
    values = values or {}
    return [{"indicator_id": r["indicator_id"], "value": values.get(r["indicator_id"], i % 4),
             "observation": "", "details": None} for i, r in enumerate(data["rows"])]
