import os
from datetime import datetime, timedelta, timezone
from typing import Generator, Tuple

from fastapi.testclient import TestClient
from jose import jwt
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

os.environ.setdefault("WEB_SENTINEL_POSTGRES_URL", "sqlite:///./test_admin_auth.db")
os.environ.setdefault("WEB_SENTINEL_ADMIN_JWT_SECRET", "test-secret")
os.environ.setdefault("WEB_SENTINEL_ADMIN_OIDC_CLIENT_SECRET", "sso-secret")
os.environ.setdefault("WEB_SENTINEL_ADMIN_OIDC_AUDIENCE", "web-sentinel-admin")
os.environ.setdefault("WEB_SENTINEL_ADMIN_OIDC_ISSUER", "https://sso.web-sentinel.test")
os.environ.setdefault("WEB_SENTINEL_ADMIN_OIDC_ALLOWED_GROUPS", '["admins","support"]')

from admin.backend.app.config import settings
from admin.backend.app.database import get_session
from admin.backend.app.main import create_app
from admin.backend.app.models import Base
from admin.backend.app.models.admin_user import AdminUser
from admin.backend.app.security import hash_password

engine = create_engine(os.environ["WEB_SENTINEL_POSTGRES_URL"], future=True)
TestingSessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)

Base.metadata.create_all(bind=engine)


def override_session() -> Generator:
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()


app = create_app()
app.dependency_overrides[get_session] = override_session
client = TestClient(app)


def setup_module() -> None:
    Base.metadata.create_all(bind=engine)
    with TestingSessionLocal() as session:
        session.query(AdminUser).delete()
        session.add(
            AdminUser(
                email="sysop@web-sentinel.com",
                hashed_password=hash_password("secret"),
                role="super-admin",
            )
        )
        session.commit()


def teardown_module() -> None:
    Base.metadata.drop_all(bind=engine)


def _login(email: str = "sysop@web-sentinel.com", password: str = "secret") -> str:
    response = client.post(
        "/api/v1/auth/login",
        data={"username": email, "password": password},
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    assert response.status_code == 200
    return response.json()["access_token"]


def _build_id_token(
    email: str,
    *,
    groups: Tuple[str, ...] = ("admins",),
    mfa: bool = True,
    role: str = "analyst",
) -> str:
    now = datetime.now(timezone.utc)
    payload = {
        "iss": settings.admin_oidc_issuer,
        "aud": settings.admin_oidc_audience,
        "email": email,
        "groups": list(groups),
        "role": role,
        settings.admin_oidc_mfa_claim: mfa,
        "iat": int(now.timestamp()),
        "exp": int((now + timedelta(minutes=5)).timestamp()),
        "nonce": "12345",
    }
    return jwt.encode(payload, settings.admin_oidc_client_secret, algorithm="HS256")


def test_failed_login_is_logged():
    # Tentative avec mot de passe invalide
    response = client.post(
        "/api/v1/auth/login",
        data={"username": "sysop@web-sentinel.com", "password": "wrong"},
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    assert response.status_code == 401

    # Connexion réussie pour consulter les journaux
    token = _login()
    logs = client.get("/api/v1/logs/", headers={"Authorization": f"Bearer {token}"})
    assert logs.status_code == 200

    entries = logs.json()
    assert any(
        entry["action"] == "admin_login_failed" and entry["details"].get("reason") == "invalid_password"
        for entry in entries
    )
    assert any(entry["action"] == "admin_login" for entry in entries)


def test_login_accepts_allowed_domain():
    with TestingSessionLocal.begin() as session:
        session.add(
            AdminUser(
                email="analyst@web-sentinel.com",
                hashed_password=hash_password("p@ssword"),
                role="analyst",
            )
        )

    response = client.post(
        "/api/v1/auth/login",
        data={"username": "analyst@web-sentinel.com", "password": "p@ssword"},
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    assert response.status_code == 200


def test_oidc_login_creates_admin_and_returns_access_token():
    response = client.post(
        "/api/v1/auth/oidc/login",
        json={"id_token": _build_id_token("support@web-sentinel.com", role="analyst")},
    )
    assert response.status_code == 200
    token = response.json()["access_token"]
    assert token

    dashboard = client.get("/api/v1/dashboard/summary", headers={"Authorization": f"Bearer {token}"})
    assert dashboard.status_code == 200

    with TestingSessionLocal() as session:
        admin = session.query(AdminUser).filter(AdminUser.email == "support@web-sentinel.com").one()
        assert admin.role == "analyst"


def test_oidc_login_requires_mfa():
    response = client.post(
        "/api/v1/auth/oidc/login",
        json={"id_token": _build_id_token("mfa@web-sentinel.com", mfa=False)},
    )
    assert response.status_code == 403
