"""Integration tests for app.api.v1.auth — register, login, refresh, logout, me, password reset, deactivate"""

import uuid

import pytest
import pytest_asyncio


@pytest_asyncio.fixture
async def registered_user(client):
    """Register a user and return email, password, and response data."""
    email = f"t{uuid.uuid4().hex[:6]}@test.cn"
    password = "Test1234"
    resp = await client.post("/api/v1/auth/register", json={
        "email": email, "password": password, "display_name": "TestUser",
    })
    assert resp.status_code == 200
    data = resp.json()
    return {
        "email": email,
        "password": password,
        "access_token": data["token"]["access_token"],
        "refresh_token": data["token"]["refresh_token"],
        "user_id": data["user"]["id"],
        "headers": {"Authorization": f"Bearer {data['token']['access_token']}"},
    }


@pytest.mark.asyncio
async def test_register(client):
    """Register creates user, returns token and tenant"""
    resp = await client.post("/api/v1/auth/register", json={
        "email": "newdoc@test.cn", "password": "Test1234", "display_name": "张主任",
    })
    assert resp.status_code == 200
    data = resp.json()
    assert "token" in data
    assert data["user"]["display_name"] == "张主任"
    assert len(data["tenants"]) == 1
    assert data["tenants"][0]["role"] == "owner"


@pytest.mark.asyncio
async def test_login(client, registered_user):
    """Login returns valid tokens"""
    resp = await client.post("/api/v1/auth/login", json={
        "email": registered_user["email"],
        "password": registered_user["password"],
    })
    assert resp.status_code == 200
    data = resp.json()
    assert data["token"]["token_type"] == "bearer"
    assert len(data["token"]["access_token"]) > 50


@pytest.mark.asyncio
async def test_login_wrong_password(client, registered_user):
    """Login with wrong password returns 401"""
    resp = await client.post("/api/v1/auth/login", json={
        "email": registered_user["email"], "password": "WrongPw1",
    })
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_login_inactive_user(client, db):
    """Login with inactive account returns 403"""
    from app.models.user import User
    from sqlalchemy import select

    email = f"t{uuid.uuid4().hex[:6]}@test.cn"
    await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Test1234", "display_name": "Inactive",
    })
    # Deactivate user directly in DB
    r = await db.execute(select(User).where(User.email == email))
    u = r.scalar()
    u.is_active = False
    await db.commit()

    resp = await client.post("/api/v1/auth/login", json={
        "email": email, "password": "Test1234",
    })
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_duplicate_register(client):
    """Duplicate email returns 409"""
    await client.post("/api/v1/auth/register", json={
        "email": "dup@test.cn", "password": "Test1234", "display_name": "Test",
    })
    resp = await client.post("/api/v1/auth/register", json={
        "email": "dup@test.cn", "password": "Test1234", "display_name": "Test2",
    })
    assert resp.status_code == 409


@pytest.mark.asyncio
async def test_token_refresh(client, registered_user):
    """Refresh with valid refresh token returns new access token"""
    resp = await client.post("/api/v1/auth/refresh", json={
        "refresh_token": registered_user["refresh_token"],
    })
    assert resp.status_code == 200
    assert "access_token" in resp.json()


@pytest.mark.asyncio
async def test_token_refresh_invalid(client):
    """Refresh with invalid token returns 401"""
    resp = await client.post("/api/v1/auth/refresh", json={
        "refresh_token": "invalid-token",
    })
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_logout(client, registered_user):
    """Logout blacklists the access token"""
    resp = await client.post("/api/v1/auth/logout", headers=registered_user["headers"])
    assert resp.status_code == 200
    assert resp.json()["status"] == "logged_out"


@pytest.mark.asyncio
async def test_login_multiple_tenants(client, db):
    """Login returns all tenants when user belongs to multiple."""
    email = f"t{uuid.uuid4().hex[:6]}@test.cn"
    r = await client.post("/api/v1/auth/register", json={
        "email": email, "password": "Test1234", "display_name": "Multi",
    })
    assert r.status_code == 200
    data = r.json()
    uid = uuid.UUID(data["user"]["id"])
    tid1 = uuid.UUID(data["tenants"][0]["id"])

    # Create a second tenant and add user
    from app.models.user import Tenant, UserTenant
    tid2 = uuid.uuid4()
    db.add(Tenant(id=tid2, name="Second Space", slug=f"second-{uuid.uuid4().hex[:8]}"))
    db.add(UserTenant(user_id=uid, tenant_id=tid2, role="viewer"))
    await db.commit()

    resp = await client.post("/api/v1/auth/login", json={
        "email": email, "password": "Test1234",
    })
    assert resp.status_code == 200
    assert len(resp.json()["tenants"]) == 2


@pytest.mark.asyncio
async def test_refresh_with_access_token(client, registered_user):
    """Refresh endpoint with access token (not refresh) returns 401."""
    # Clear cookies so the body token takes effect (otherwise valid
    # refresh_token cookie would be used first)
    client.cookies.clear()
    resp = await client.post("/api/v1/auth/refresh", json={
        "refresh_token": registered_user["access_token"],
    })
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_reset_password_user_not_found(client):
    """Reset password with token referencing deleted user returns 404."""
    from app.api.v1.auth import _store_reset_token, _del_reset_token
    await _store_reset_token("user-deleted-test", "00000000-0000-0000-0000-000000000000")
    resp = await client.post("/api/v1/auth/reset-password", json={
        "token": "user-deleted-test",
        "new_password": "Newpass789",
    })
    assert resp.status_code == 404
    await _del_reset_token("user-deleted-test")


@pytest.mark.asyncio
async def test_forgot_password(client, registered_user):
    """Forgot password returns reset link in dev mode"""
    resp = await client.post("/api/v1/auth/forgot-password", json={
        "email": registered_user["email"],
    })
    assert resp.status_code == 200
    data = resp.json()
    assert data["status"] == "sent"
    assert "reset_link" in data  # dev mode returns link


@pytest.mark.asyncio
async def test_forgot_password_nonexistent(client):
    """Forgot password returns sent even if email doesn't exist"""
    resp = await client.post("/api/v1/auth/forgot-password", json={
        "email": "nobody@nowhere.cn",
    })
    assert resp.status_code == 200
    assert resp.json()["status"] == "sent"


@pytest.mark.asyncio
async def test_reset_password(client, registered_user):
    """Reset password flow (forgot → reset)"""
    # Step 1: forgot password to get the reset link
    resp = await client.post("/api/v1/auth/forgot-password", json={
        "email": registered_user["email"],
    })
    link = resp.json()["reset_link"]
    token = link.split("token=")[1]

    # Step 2: reset with the token
    resp = await client.post("/api/v1/auth/reset-password", json={
        "token": token,
        "new_password": "Newpass789",
    })
    assert resp.status_code == 200
    assert resp.json()["status"] == "ok"

    # Step 3: login with new password
    resp = await client.post("/api/v1/auth/login", json={
        "email": registered_user["email"], "password": "Newpass789",
    })
    assert resp.status_code == 200


@pytest.mark.asyncio
async def test_reset_password_invalid_token(client):
    """Reset password with invalid token returns 400"""
    resp = await client.post("/api/v1/auth/reset-password", json={
        "token": "bad-token",
        "new_password": "Newpass789",
    })
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_reset_password_short_password(client, registered_user):
    """Reset password with too-short password returns 400"""
    resp = await client.post("/api/v1/auth/forgot-password", json={
        "email": registered_user["email"],
    })
    token = resp.json()["reset_link"].split("token=")[1]

    resp = await client.post("/api/v1/auth/reset-password", json={
        "token": token,
        "new_password": "ab",
    })
    assert resp.status_code == 422  # Pydantic validation rejects short password


@pytest.mark.asyncio
async def test_get_me(client, registered_user):
    """Get current user profile"""
    resp = await client.get("/api/v1/auth/me", headers=registered_user["headers"])
    assert resp.status_code == 200
    data = resp.json()
    assert data["email"] == registered_user["email"]
    assert data["display_name"] == "TestUser"


@pytest.mark.asyncio
async def test_update_me(client, registered_user):
    """Update user profile"""
    resp = await client.put("/api/v1/auth/me", json={
        "display_name": "UpdatedName",
        "title": "Dr.",
    }, headers=registered_user["headers"])
    assert resp.status_code == 200
    assert resp.json()["status"] == "updated"

    # Verify change persisted
    resp = await client.get("/api/v1/auth/me", headers=registered_user["headers"])
    assert resp.json()["display_name"] == "UpdatedName"
    assert resp.json()["title"] == "Dr."


@pytest.mark.asyncio
async def test_deactivate_account(client, registered_user):
    """Deactivate account soft-deletes the user"""
    resp = await client.post("/api/v1/auth/deactivate", json={
        "confirm": True,
    }, headers=registered_user["headers"])
    assert resp.status_code == 200
    assert resp.json()["status"] == "deactivated"

    # Can no longer login
    resp = await client.post("/api/v1/auth/login", json={
        "email": registered_user["email"],
        "password": registered_user["password"],
    })
    assert resp.status_code == 403
