import hashlib
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.api.deps import get_current_user
from app.core.security import (
    create_access_token,
    hash_password,
    verify_password,
)
from app.db.session import get_db
from app.models.user import User
from app.models.platform_staff import PlatformRoleDefinition, PlatformStaffInvitation
from app.schemas.auth import (
    AcceptPlatformStaffInvitationRequest,
    ForgotPasswordRequest,
    LoginRequest,
    LoginResponse,
    MessageResponse,
    ResendVerificationRequest,
    ResetPasswordRequest,
    SignupRequest,
    UserResponse,
    VerifyEmailRequest,
)
from app.services.platform_access import PLATFORM_PERMISSIONS, SYSTEM_ROLE_PERMISSIONS
from app.services.platform_audit import record_platform_audit
from app.services.email_sender import (
    send_password_reset_email,
    send_verification_email,
)
from app.services.email_verification import (
    create_email_verification,
    verify_email_code,
)
from app.services.password_reset import (
    create_password_reset,
    reset_password_with_token,
)


router = APIRouter(
    prefix="/api/auth",
    tags=["Authentication"],
)

def _invitation(db: Session, token: str, email: str):
    token_hash = hashlib.sha256(token.encode()).hexdigest()
    invitation = db.scalar(select(PlatformStaffInvitation).where(PlatformStaffInvitation.token_hash == token_hash).with_for_update())
    now = datetime.now(timezone.utc)
    if invitation is None or invitation.revoked_at is not None or invitation.accepted_at is not None or invitation.expires_at <= now:
        raise HTTPException(422, "This platform staff invitation is invalid or expired.")
    if invitation.email.lower() != email.lower():
        raise HTTPException(422, "Use the exact email address that received this invitation.")
    role = invitation.role_key.strip().upper()
    if role == "CEO":
        raise HTTPException(422, "The CEO role cannot be granted through an invitation.")
    if role not in SYSTEM_ROLE_PERMISSIONS and not db.scalar(select(PlatformRoleDefinition).where(PlatformRoleDefinition.key == role, PlatformRoleDefinition.is_active.is_(True))):
        raise HTTPException(422, "The invitation role is no longer active.")
    permissions = sorted(set(invitation.permission_overrides or []))
    if set(permissions) - PLATFORM_PERMISSIONS:
        raise HTTPException(422, "This invitation contains invalid permissions.")
    return invitation, role, permissions

def _accept_invitation(db: Session, user: User, invitation, role: str, permissions: list[str]):
    user.is_platform_admin = True; user.platform_role = role; user.platform_permissions = permissions
    user.platform_staff_status = "ACTIVE"; user.platform_session_version += 1
    invitation.accepted_at = datetime.now(timezone.utc); invitation.accepted_by = user.id
    record_platform_audit(db, user.id, "PLATFORM_STAFF_INVITATION_ACCEPTED", "platform_staff_invitation", invitation.id, details={"role_key": role, "email": user.email})


@router.post(
    "/signup",
    response_model=UserResponse,
    status_code=status.HTTP_201_CREATED,
)
def signup(
    payload: SignupRequest,
    db: Session = Depends(get_db),
):
    email = str(payload.email).strip().lower()

    existing_user = db.scalar(
        select(User).where(User.email == email)
    )

    if existing_user:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="An account with this email already exists.",
        )

    invitation_data = _invitation(db, payload.platform_invite_token, email) if payload.platform_invite_token else None
    user = User(
        full_name=payload.full_name.strip(),
        email=email,
        password_hash=hash_password(payload.password),
    )

    db.add(user)
    db.flush()
    if invitation_data:
        _accept_invitation(db, user, *invitation_data)
    db.commit()
    db.refresh(user)

    verification_code = create_email_verification(
        db=db,
        user=user,
    )

    send_verification_email(
        user.email,
        verification_code,
    )

    return user


@router.post(
    "/login",
    response_model=LoginResponse,
)
def login(
    payload: LoginRequest,
    db: Session = Depends(get_db),
):
    email = str(payload.email).strip().lower()

    user = db.scalar(
        select(User).where(User.email == email)
    )

    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid email or password.",
        )

    if not verify_password(
        payload.password,
        user.password_hash,
    ):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid email or password.",
        )

    if not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="This account is disabled.",
        )

    if not user.is_email_verified:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Please verify your email before signing in.",
        )

    if user.is_platform_admin and user.platform_staff_status != "ACTIVE":
        raise HTTPException(status_code=403, detail="This platform staff account no longer has sign-in access.")
    if user.is_platform_admin:
        user.platform_last_login_at = datetime.now(timezone.utc); db.commit()
    access_token = create_access_token(str(user.id), user.platform_session_version)

    return LoginResponse(
        access_token=access_token,
        user=user,
    )

@router.post("/platform-staff-invitations/accept", response_model=LoginResponse)
def accept_platform_invitation(payload: AcceptPlatformStaffInvitationRequest, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
    if not current_user.is_email_verified:
        raise HTTPException(403, "Verify your email before accepting an invitation.")
    invitation_data = _invitation(db, payload.platform_invite_token, current_user.email)
    _accept_invitation(db, current_user, *invitation_data)
    db.commit(); db.refresh(current_user)
    return LoginResponse(access_token=create_access_token(str(current_user.id), current_user.platform_session_version), user=current_user)


@router.post(
    "/verify-email",
    response_model=MessageResponse,
)
def verify_email(
    payload: VerifyEmailRequest,
    db: Session = Depends(get_db),
):
    email = str(payload.email).strip().lower()

    user = db.scalar(
        select(User).where(User.email == email)
    )

    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Account not found.",
        )

    if user.is_email_verified:
        return MessageResponse(
            message="Email is already verified."
        )

    verified = verify_email_code(
        db=db,
        user=user,
        code=payload.code,
    )

    if not verified:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid or expired verification code.",
        )

    return MessageResponse(
        message="Email verified successfully."
    )


@router.post(
    "/resend-verification",
    response_model=MessageResponse,
)
def resend_verification(
    payload: ResendVerificationRequest,
    db: Session = Depends(get_db),
):
    email = str(payload.email).strip().lower()

    user = db.scalar(
        select(User).where(User.email == email)
    )

    if not user:
        return MessageResponse(
            message=(
                "If an account exists, "
                "a verification code has been sent."
            )
        )

    if user.is_email_verified:
        return MessageResponse(
            message="Email is already verified."
        )

    verification_code = create_email_verification(
        db=db,
        user=user,
    )

    send_verification_email(
        user.email,
        verification_code,
    )

    return MessageResponse(
        message="A new verification code has been sent."
    )


@router.post(
    "/forgot-password",
    response_model=MessageResponse,
)
def forgot_password(
    payload: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    email = str(payload.email).strip().lower()

    user = db.scalar(
        select(User).where(User.email == email)
    )

    if user and user.is_active:
        reset_token = create_password_reset(
            db=db,
            user=user,
        )

        send_password_reset_email(
            user.email,
            reset_token,
        )

    return MessageResponse(
        message=(
            "If an account exists with that email, "
            "a password reset link has been sent."
        )
    )


@router.post(
    "/reset-password",
    response_model=MessageResponse,
)
def reset_password(
    payload: ResetPasswordRequest,
    db: Session = Depends(get_db),
):
    email = str(payload.email).strip().lower()

    user = db.scalar(
        select(User).where(User.email == email)
    )

    if not user or not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid or expired password reset link.",
        )

    reset_successful = reset_password_with_token(
        db=db,
        user=user,
        token=payload.token,
        new_password=payload.new_password,
    )

    if not reset_successful:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid or expired password reset link.",
        )

    return MessageResponse(
        message="Password reset successfully."
    )


@router.get(
    "/me",
    response_model=UserResponse,
)
def get_me(
    current_user: User = Depends(get_current_user),
):
    return current_user
