import hashlib
import uuid
from datetime import datetime, timedelta, timezone

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import delete, select

from app.core.security import create_access_token, hash_password
from app.db.session import SessionLocal
from app.main import app
from app.models.platform_audit import PlatformAuditLog
from app.models.platform_staff import PlatformRoleDefinition, PlatformStaffInvitation
from app.models.user import User

client = TestClient(app)

def headers(user):
    return {"Authorization": f"Bearer {create_access_token(str(user.id), user.platform_session_version)}"}

def make_user(db, role=None, permissions=None, status_value="ACTIVE"):
    user = User(full_name="Platform Test", email=f"{uuid.uuid4().hex}@example.com", password_hash=hash_password("Password123!"), is_active=True, is_email_verified=True, is_platform_admin=bool(role), platform_role=role, platform_permissions=permissions or [], platform_staff_status=status_value if role else None)
    db.add(user); db.commit(); db.refresh(user); return user

@pytest.fixture
def db():
    session = SessionLocal()
    try:
        yield session
    finally:
        session.rollback()
        session.execute(delete(PlatformAuditLog))
        session.execute(delete(PlatformStaffInvitation))
        session.execute(delete(PlatformRoleDefinition))
        session.execute(delete(User).where(User.email.like("%@example.com")))
        session.commit(); session.close()

def test_business_user_cannot_access_platform_api(db):
    user = make_user(db)
    assert client.get("/api/admin/me", headers=headers(user)).status_code == 403

def test_custom_role_without_platform_view_cannot_view_data(db):
    role = PlatformRoleDefinition(key="NO_VIEW", name="No view", permissions=["staff.read"], is_active=True)
    db.add(role); db.commit()
    user = make_user(db, "NO_VIEW")
    assert client.get("/api/admin/staff", headers=headers(user)).status_code == 403

def test_backend_permission_enforcement_and_no_self_escalation(db):
    custom = PlatformRoleDefinition(key="VIEWER_CUSTOM", name="Viewer", permissions=["platform.view"], is_active=True)
    db.add(custom); db.commit()
    viewer = make_user(db, "VIEWER_CUSTOM")
    assert client.get("/api/admin/me", headers=headers(viewer)).status_code == 200
    response = client.patch(f"/api/admin/staff/{viewer.id}/access", headers=headers(viewer), json={"role_key": "SUPER_ADMIN", "permission_overrides": [], "reason": "self escalation"})
    assert response.status_code == 403

def test_cannot_revoke_or_reset_higher_rank(db):
    ceo = make_user(db, "CEO")
    operations = make_user(db, "OPERATIONS", ["security.manage"])
    payload = {"reason": "security check"}
    assert client.post(f"/api/admin/staff/{ceo.id}/revoke-sessions", headers=headers(operations), json=payload).status_code == 403
    assert client.post(f"/api/admin/staff/{ceo.id}/password-reset", headers=headers(operations), json=payload).status_code == 403

def test_last_active_super_admin_is_protected(db):
    admin = make_user(db, "SUPER_ADMIN")
    response = client.post(f"/api/admin/staff/{admin.id}/suspend", headers=headers(admin), json={"reason": "must remain"})
    assert response.status_code == 403
    other = make_user(db, "OPERATIONS")
    # A promoted CEO cannot remove the only other active Super Admin.
    ceo = make_user(db, "CEO")
    db.delete(admin); db.commit()
    other.platform_role = "SUPER_ADMIN"; db.commit()
    response = client.post(f"/api/admin/staff/{other.id}/suspend", headers=headers(ceo), json={"reason": "must remain"})
    assert response.status_code == 409

def test_invitation_email_expiry_and_one_time_acceptance(db):
    inviter = make_user(db, "CEO")
    raw = "a" * 43
    invited = make_user(db)
    invitation = PlatformStaffInvitation(email=invited.email, role_key="READ_ONLY", permission_overrides=[], token_hash=hashlib.sha256(raw.encode()).hexdigest(), invited_by=inviter.id, expires_at=datetime.now(timezone.utc) + timedelta(hours=1))
    db.add(invitation); db.commit()
    response = client.post("/api/auth/platform-staff-invitations/accept", headers=headers(invited), json={"platform_invite_token": raw})
    assert response.status_code == 200
    assert client.post("/api/auth/platform-staff-invitations/accept", headers={"Authorization": f"Bearer {response.json()['access_token']}"}, json={"platform_invite_token": raw}).status_code == 422
    wrong = make_user(db)
    expired = PlatformStaffInvitation(email=wrong.email, role_key="READ_ONLY", permission_overrides=[], token_hash=hashlib.sha256(("b" * 43).encode()).hexdigest(), invited_by=inviter.id, expires_at=datetime.now(timezone.utc) - timedelta(seconds=1))
    db.add(expired); db.commit()
    assert client.post("/api/auth/platform-staff-invitations/accept", headers=headers(wrong), json={"platform_invite_token": "b" * 43}).status_code == 422

def test_suspension_revokes_existing_session_and_audits(db):
    ceo = make_user(db, "CEO")
    target = make_user(db, "OPERATIONS")
    old_headers = headers(target)
    response = client.post(f"/api/admin/staff/{target.id}/suspend", headers=headers(ceo), json={"reason": "investigation"})
    assert response.status_code == 200
    assert client.get("/api/admin/me", headers=old_headers).status_code == 401
    audit = db.scalar(select(PlatformAuditLog).where(PlatformAuditLog.action == "PLATFORM_STAFF_SUSPENDED", PlatformAuditLog.entity_id == target.id))
    assert audit is not None and audit.actor_user_id == ceo.id
