"""Integration tests for the auth router — registration, login, profile."""

import uuid
from datetime import datetime, timedelta, timezone

import pytest
import pytest_asyncio
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

from models import InviteCode, User


# ── Registration ─────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_register_valid(client, invite_code):
    """Register with a valid invite code → 201 + user created."""
    resp = await client.post("/api/v1/auth/register", json={
        "email": "newuser@example.com",
        "password": "strongpass1",
        "display_name": "New User",
        "invite_code": invite_code,
    })
    assert resp.status_code == 201
    data = resp.json()
    assert data["email"] == "newuser@example.com"
    assert data["display_name"] == "New User"
    assert data["role"] == "creator"
    assert "id" in data
    # Password not leaked
    assert "hashed_password" not in data


@pytest.mark.asyncio
async def test_register_invalid_invite_code(client, invite_code):
    """Register with a wrong invite code → 403."""
    resp = await client.post("/api/v1/auth/register", json={
        "email": "bad@example.com",
        "password": "strongpass1",
        "display_name": "Bad",
        "invite_code": "WRONG-CODE",
    })
    assert resp.status_code == 403
    assert "Invalid invite code" in resp.json()["detail"]


@pytest.mark.asyncio
async def test_register_expired_invite_code(client, db_engine):
    """Register with an expired invite code → 403."""
    factory = async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        past = (datetime.now(timezone.utc) - timedelta(days=1)).replace(tzinfo=None)
        session.add(InviteCode(code="EXPIRED-CODE", uses_remaining=10, expires_at=past))
        await session.commit()

    resp = await client.post("/api/v1/auth/register", json={
        "email": "exp@example.com",
        "password": "strongpass1",
        "display_name": "Expired",
        "invite_code": "EXPIRED-CODE",
    })
    assert resp.status_code == 403
    assert "expired" in resp.json()["detail"].lower()


@pytest.mark.asyncio
async def test_register_exhausted_invite_code(client, db_engine):
    """Register with an invite code that has uses_remaining=0 → 403."""
    factory = async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        session.add(InviteCode(code="EXHAUSTED", uses_remaining=0))
        await session.commit()

    resp = await client.post("/api/v1/auth/register", json={
        "email": "nope@example.com",
        "password": "strongpass1",
        "display_name": "Nope",
        "invite_code": "EXHAUSTED",
    })
    assert resp.status_code == 403
    assert "exhausted" in resp.json()["detail"].lower()


@pytest.mark.asyncio
async def test_register_invite_code_decrements(client, db_engine):
    """Invite code uses_remaining decrements after registration."""
    factory = async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        session.add(InviteCode(code="SINGLE-USE", uses_remaining=1))
        await session.commit()

    # First registration succeeds
    resp = await client.post("/api/v1/auth/register", json={
        "email": "first@example.com",
        "password": "strongpass1",
        "display_name": "First",
        "invite_code": "SINGLE-USE",
    })
    assert resp.status_code == 201

    # Second registration with same code fails (exhausted)
    resp = await client.post("/api/v1/auth/register", json={
        "email": "second@example.com",
        "password": "strongpass1",
        "display_name": "Second",
        "invite_code": "SINGLE-USE",
    })
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_register_duplicate_email(client, invite_code, registered_user):
    """Register with an already-used email → 409."""
    resp = await client.post("/api/v1/auth/register", json={
        "email": "testuser@chrysopedia.com",
        "password": "anotherpass1",
        "display_name": "Dup",
        "invite_code": invite_code,
    })
    assert resp.status_code == 409
    assert "already registered" in resp.json()["detail"].lower()


# ── Login ────────────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_login_success(client, registered_user):
    """Login with correct credentials → 200 + JWT."""
    resp = await client.post("/api/v1/auth/login", json={
        "email": "testuser@chrysopedia.com",
        "password": "securepass123",
    })
    assert resp.status_code == 200
    data = resp.json()
    assert "access_token" in data
    assert data["token_type"] == "bearer"


@pytest.mark.asyncio
async def test_login_wrong_password(client, registered_user):
    """Login with wrong password → 401."""
    resp = await client.post("/api/v1/auth/login", json={
        "email": "testuser@chrysopedia.com",
        "password": "wrongpassword",
    })
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_login_nonexistent_email(client):
    """Login with an email that doesn't exist → 401."""
    resp = await client.post("/api/v1/auth/login", json={
        "email": "nobody@example.com",
        "password": "somepass123",
    })
    assert resp.status_code == 401


# ── Profile (GET /me) ───────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_get_me_authenticated(client, auth_headers):
    """GET /me with valid token → 200 + profile."""
    resp = await client.get("/api/v1/auth/me", headers=auth_headers)
    assert resp.status_code == 200
    data = resp.json()
    assert data["email"] == "testuser@chrysopedia.com"
    assert data["display_name"] == "Test User"


@pytest.mark.asyncio
async def test_get_me_no_token(client, db_engine):
    """GET /me without token → 401."""
    resp = await client.get("/api/v1/auth/me")
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_get_me_invalid_token(client, db_engine):
    """GET /me with garbage token → 401."""
    resp = await client.get("/api/v1/auth/me", headers={
        "Authorization": "Bearer invalid.garbage.token",
    })
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_get_me_expired_token(client, db_engine, invite_code):
    """GET /me with an expired JWT → 401."""
    from auth import create_access_token

    # Register a user first
    resp = await client.post("/api/v1/auth/register", json={
        "email": "expired@example.com",
        "password": "strongpass1",
        "display_name": "Expired Token User",
        "invite_code": invite_code,
    })
    assert resp.status_code == 201
    user_id = resp.json()["id"]

    # Create a token that expires immediately
    token = create_access_token(user_id, "creator", expires_minutes=-1)
    resp = await client.get("/api/v1/auth/me", headers={
        "Authorization": f"Bearer {token}",
    })
    assert resp.status_code == 401


# ── Profile (PUT /me) ───────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_update_display_name(client, auth_headers):
    """PUT /me updates display_name → 200 + new name."""
    resp = await client.put("/api/v1/auth/me", json={
        "display_name": "Updated Name",
    }, headers=auth_headers)
    assert resp.status_code == 200
    assert resp.json()["display_name"] == "Updated Name"

    # Verify persistence
    resp2 = await client.get("/api/v1/auth/me", headers=auth_headers)
    assert resp2.json()["display_name"] == "Updated Name"


@pytest.mark.asyncio
async def test_update_password(client, invite_code):
    """PUT /me changes password → can login with new password."""
    # Register
    await client.post("/api/v1/auth/register", json={
        "email": "pwchange@example.com",
        "password": "oldpassword1",
        "display_name": "PW User",
        "invite_code": invite_code,
    })
    # Login
    login_resp = await client.post("/api/v1/auth/login", json={
        "email": "pwchange@example.com",
        "password": "oldpassword1",
    })
    token = login_resp.json()["access_token"]
    headers = {"Authorization": f"Bearer {token}"}

    # Change password
    resp = await client.put("/api/v1/auth/me", json={
        "current_password": "oldpassword1",
        "new_password": "newpassword1",
    }, headers=headers)
    assert resp.status_code == 200

    # Old password fails
    resp_old = await client.post("/api/v1/auth/login", json={
        "email": "pwchange@example.com",
        "password": "oldpassword1",
    })
    assert resp_old.status_code == 401

    # New password works
    resp_new = await client.post("/api/v1/auth/login", json={
        "email": "pwchange@example.com",
        "password": "newpassword1",
    })
    assert resp_new.status_code == 200


# ── Malformed inputs (422 validation) ───────────────────────────────────────


@pytest.mark.asyncio
async def test_register_missing_fields(client):
    """Register with missing fields → 422."""
    resp = await client.post("/api/v1/auth/register", json={})
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_register_empty_password(client):
    """Register with empty password → 422 (min_length=8)."""
    resp = await client.post("/api/v1/auth/register", json={
        "email": "a@b.com",
        "password": "",
        "display_name": "X",
        "invite_code": "CODE",
    })
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_login_empty_body(client):
    """Login with empty body → 422."""
    resp = await client.post("/api/v1/auth/login", json={})
    assert resp.status_code == 422


# ── Public endpoints unaffected ──────────────────────────────────────────────


@pytest.mark.asyncio
async def test_public_techniques_no_auth(client, db_engine):
    """GET /api/v1/techniques works without auth."""
    resp = await client.get("/api/v1/techniques")
    # 200 even if empty — no 401/403
    assert resp.status_code == 200


@pytest.mark.asyncio
async def test_public_creators_no_auth(client, db_engine):
    """GET /api/v1/creators works without auth."""
    resp = await client.get("/api/v1/creators")
    assert resp.status_code == 200
