from datetime import timedelta, datetime, timezone
from typing import Annotated

import phonenumbers
from fastapi import APIRouter, HTTPException, Request
from fastapi import status, Depends
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr

from repositories.otp_repository import OTPRepository
from repositories.user_repository import UserRepository
from schemas.auth import (
    UserCreate,
    UserRead,
    RecoverPasswordRequest,
    VerifyOTPRequest,
    UpdatePasswordRequest,
)
from services.email_service import send_signup_otp_email, send_forgot_password_otp_email
from utils.auth_dependency import user_dependency
from utils.constants import OTPType, UserStatus
from utils.database import db_dependency
from utils.encryption import bcrypt_context
from utils.exceptions import (
    UnauthorizedException,
    InvalidException,
    NotFoundException,
    AuthorizationError,
)
from utils.messages import Message
from utils.response import Response, LoginResponseModel, ObjectResponseModel
from models import Users

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


@router.post("/login", response_model=LoginResponseModel)
async def login_for_access_token(request: Request, db: db_dependency):
    try:
        # Try form data first
        form = await request.form()
        username = form.get("username")
        password = form.get("password")
    except Exception:
        username = None
        password = None

    if not username or not password:
        # Try JSON body if form fields are missing
        data = await request.json()
        username = data.get("username")
        password = data.get("password")

    if not username or not password:
        raise UnauthorizedException("Username and password required")

    user_repo = UserRepository(db)
    user = await user_repo.authenticate(username, password)
    if not user:
        raise UnauthorizedException

    if user.status == UserStatus.INACTIVE or user.status == UserStatus.DELETED:
        raise UnauthorizedException("User is inactive or deleted")

    if not user.is_email_verified:
        otp_repo = OTPRepository(db)
        otp_log = await otp_repo.create_otp(user.email, OTPType.SIGNUP)
        send_signup_otp_email(to_email=user.email, otp=otp_log.otp, name=user.name)
        raise AuthorizationError(
            "An Otp has been sent to your email for verification. Please verify your email to login."
        )

    token = user_repo.create_access_token(
        user.name, user.id, user.email, timedelta(days=1)
    )
    return Response.login_user(
        user=(
            {"email": user.email, "name": user.name, "step": user.step}
            if user
            else None
        ),
        access_token=token,
    )


@router.post(
    "/register", status_code=status.HTTP_201_CREATED, response_model=LoginResponseModel
)
async def create_user(db: db_dependency, create_user_request: UserCreate):
    user_repo = UserRepository(db)
    user_data = UserRead(
        name=create_user_request.name,
        email=create_user_request.email,
        password=bcrypt_context.hash(create_user_request.password),
        status=1,
        created_at=datetime.now(timezone.utc),
        updated_at=datetime.now(timezone.utc),
        deleted_at=None,
    )
    user = await user_repo.create(user_data)
    otp_repo = OTPRepository(db)
    otp_log = await otp_repo.create_otp(user.email, OTPType.SIGNUP)
    send_signup_otp_email(to_email=user.email, otp=otp_log.otp, name=user_data.name)
    token = user_repo.create_access_token(
        user.name, user.id, user.email, timedelta(days=1)
    )
    
    return Response.login_user(
        message=Message.Success.USER_REGISTER,
        user=(
            {
                "email": user.email,
                "name": user.name,
                "step": user.step,
            }
            if user
            else None
        ),
        access_token=token,
    )



@router.post(
    "/recover-password",
    status_code=status.HTTP_200_OK,
    response_model=ObjectResponseModel,
)
async def recover_password(request: RecoverPasswordRequest, db: db_dependency):
    user = db.query(Users).filter(Users.email == request.email).first()
    if not user:
        return Response.post(
            message=Message.Success.WRONG_EMAIL
        )

    otp_repo = OTPRepository(db)
    otp_log = await otp_repo.create_otp(request.email, OTPType.PASSWORD_RESET)

    send_forgot_password_otp_email(to_email=request.email, otp=otp_log.otp)
    return Response.post(message=Message.Success.FORGOT_PASSWORD)


@router.post(
    "/verify-password-otp",
    status_code=status.HTTP_200_OK,
    response_model=ObjectResponseModel,
)
async def verify_password_otp(request: VerifyOTPRequest, db: db_dependency):
    otp_repo = OTPRepository(db)
    otp_log = await otp_repo.get_latest_otp(
        request.email, otp_type=OTPType.PASSWORD_RESET
    )

    if (
        not otp_log
        or otp_log.otp != request.otp
        or otp_log.used_at is not None
        or otp_repo.is_otp_expired(otp_log.expired_at)
    ):
        raise InvalidException("Otp")
    return Response.post(message=Message.Success.OTP_IS_VERIFIED)


@router.post(
    "/verify-signup-otp",
    status_code=status.HTTP_200_OK,
    response_model=LoginResponseModel,
)
async def verify_signup_otp(request: VerifyOTPRequest, db: db_dependency):
    otp_repo = OTPRepository(db)
    otp_log = await otp_repo.get_latest_otp(request.email, otp_type=OTPType.SIGNUP)
    if (
        not otp_log
        or otp_log.otp != request.otp
        or otp_log.used_at is not None
        or otp_repo.is_otp_expired(otp_log.expired_at)
    ):
        raise InvalidException("Otp")

    await otp_repo.mark_otp_used(otp_log)
    user_repo = UserRepository(db)

    user = await user_repo.verify_email(request.email)
    token = user_repo.create_access_token(
        user.name, user.id, user.email, timedelta(days=1)
    )
    return Response.login_user(
        message=Message.Success.OTP_IS_VERIFIED,
        user=(
            {
                "email": user.email,
                "name": user.name,
                "step": user.step,
                "is_email_verified": user.is_email_verified,
            }
            if user
            else None
        ),
        access_token=token,
    )


@router.post(
    "/update-password",
    status_code=status.HTTP_200_OK,
    response_model=ObjectResponseModel,
)
async def update_password(request: UpdatePasswordRequest, db: db_dependency):
    otp_repo = OTPRepository(db)
    otp_log = await otp_repo.get_latest_otp(
        request.email, otp_type=OTPType.PASSWORD_RESET
    )

    now = datetime.now(timezone.utc)

    if (
        not otp_log
        or otp_log.used_at is not None
        or otp_repo.is_otp_expired(otp_log.expired_at)
    ):
        raise InvalidException("Otp")

    user_repo = UserRepository(db)
    user = await user_repo.get_by_email(request.email)
    if not user:
        raise NotFoundException("User")

    user.password = bcrypt_context.hash(request.new_password)
    await user_repo.update(user.id, user)

    await otp_repo.mark_otp_used(otp_log)

    return Response.post(message=Message.Success.OBJECT_UPDATED % "Password")


@router.get("/me", response_model=dict)
async def get_current_user_info(current_user: user_dependency, db: db_dependency):
    user_repo = UserRepository(db)
    user = await user_repo.get_by_email(
        current_user.get("email"), convert_to_entity=False
    )
    return (
        {
            "name": user.name,
            "email": user.email,
            "step": user.step,
            "is_email_verified": user.is_email_verified,
        }
        if user
        else None
    )
