"""
OAuth Authentication Routes
Handles Google and Microsoft OAuth login flows
"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
from fastapi.responses import RedirectResponse
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.auth.jwt_handler import get_current_user
from app.models.user import User
from app.services.oauth_service import oauth_service
from app.config import settings

router = APIRouter()


# ============== Schemas ==============

class OAuthProvidersResponse(BaseModel):
    providers: list
    google_enabled: bool
    microsoft_enabled: bool


class OAuthLoginResponse(BaseModel):
    authorization_url: str
    state: str


class OAuthTokenResponse(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str


class OAuthCallbackResponse(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str
    user: dict


class UnlinkProviderResponse(BaseModel):
    success: bool
    message: str


# ============== Routes ==============

@router.get("/providers", response_model=OAuthProvidersResponse)
async def list_oauth_providers():
    """
    List available OAuth providers
    """
    providers = oauth_service.get_available_providers()
    return {
        "providers": providers,
        "google_enabled": "google" in providers,
        "microsoft_enabled": "microsoft" in providers
    }


@router.get("/google/login")
async def google_login(
    redirect_url: Optional[str] = Query(None, description="URL to redirect after login")
):
    """
    Initiate Google OAuth login

    Returns the authorization URL to redirect the user to
    """
    if "google" not in oauth_service.get_available_providers():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Google OAuth is not configured"
        )

    auth_url, state = oauth_service.get_authorization_url("google", redirect_url)

    return {
        "authorization_url": auth_url,
        "state": state
    }


@router.get("/google/callback")
async def google_callback(
    code: str = Query(...),
    state: str = Query(...),
    error: Optional[str] = Query(None),
    error_description: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_db)
):
    """
    Handle Google OAuth callback

    This endpoint is called by Google after user authorization.
    It exchanges the code for tokens and creates/updates the user.
    """
    if error:
        # Redirect to frontend with error
        error_url = f"{settings.FRONTEND_URL}/auth/error?provider=google&error={error}"
        if error_description:
            error_url += f"&description={error_description}"
        return RedirectResponse(url=error_url)

    try:
        user, tokens = await oauth_service.handle_callback(
            provider="google",
            code=code,
            state=state,
            db=db
        )

        # Redirect to frontend with tokens
        redirect_url = tokens.pop("redirect_url", settings.FRONTEND_URL)
        success_url = f"{redirect_url}/auth/callback?access_token={tokens['access_token']}&refresh_token={tokens['refresh_token']}&provider=google"
        return RedirectResponse(url=success_url)

    except ValueError as e:
        error_url = f"{settings.FRONTEND_URL}/auth/error?provider=google&error=invalid_state&description={str(e)}"
        return RedirectResponse(url=error_url)

    except Exception as e:
        error_url = f"{settings.FRONTEND_URL}/auth/error?provider=google&error=server_error&description={str(e)}"
        return RedirectResponse(url=error_url)


@router.post("/google/token", response_model=OAuthCallbackResponse)
async def google_token_exchange(
    code: str = Query(...),
    state: str = Query(...),
    db: AsyncSession = Depends(get_db)
):
    """
    Exchange Google OAuth code for tokens (for SPA/mobile apps)

    Alternative to callback redirect - returns JSON response
    """
    if "google" not in oauth_service.get_available_providers():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Google OAuth is not configured"
        )

    try:
        user, tokens = await oauth_service.handle_callback(
            provider="google",
            code=code,
            state=state,
            db=db
        )

        return {
            "access_token": tokens["access_token"],
            "refresh_token": tokens["refresh_token"],
            "token_type": tokens["token_type"],
            "user": {
                "id": user.id,
                "email": user.email,
                "first_name": user.first_name,
                "last_name": user.last_name,
                "avatar_url": user.avatar_url,
                "role": user.role.value,
                "oauth_provider": user.oauth_provider
            }
        }

    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e)
        )
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"OAuth error: {str(e)}"
        )


@router.get("/microsoft/login")
async def microsoft_login(
    redirect_url: Optional[str] = Query(None, description="URL to redirect after login")
):
    """
    Initiate Microsoft OAuth login

    Returns the authorization URL to redirect the user to
    """
    if "microsoft" not in oauth_service.get_available_providers():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Microsoft OAuth is not configured"
        )

    auth_url, state = oauth_service.get_authorization_url("microsoft", redirect_url)

    return {
        "authorization_url": auth_url,
        "state": state
    }


@router.get("/microsoft/callback")
async def microsoft_callback(
    code: str = Query(...),
    state: str = Query(...),
    error: Optional[str] = Query(None),
    error_description: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_db)
):
    """
    Handle Microsoft OAuth callback

    This endpoint is called by Microsoft after user authorization.
    It exchanges the code for tokens and creates/updates the user.
    """
    if error:
        error_url = f"{settings.FRONTEND_URL}/auth/error?provider=microsoft&error={error}"
        if error_description:
            error_url += f"&description={error_description}"
        return RedirectResponse(url=error_url)

    try:
        user, tokens = await oauth_service.handle_callback(
            provider="microsoft",
            code=code,
            state=state,
            db=db
        )

        redirect_url = tokens.pop("redirect_url", settings.FRONTEND_URL)
        success_url = f"{redirect_url}/auth/callback?access_token={tokens['access_token']}&refresh_token={tokens['refresh_token']}&provider=microsoft"
        return RedirectResponse(url=success_url)

    except ValueError as e:
        error_url = f"{settings.FRONTEND_URL}/auth/error?provider=microsoft&error=invalid_state&description={str(e)}"
        return RedirectResponse(url=error_url)

    except Exception as e:
        error_url = f"{settings.FRONTEND_URL}/auth/error?provider=microsoft&error=server_error&description={str(e)}"
        return RedirectResponse(url=error_url)


@router.post("/microsoft/token", response_model=OAuthCallbackResponse)
async def microsoft_token_exchange(
    code: str = Query(...),
    state: str = Query(...),
    db: AsyncSession = Depends(get_db)
):
    """
    Exchange Microsoft OAuth code for tokens (for SPA/mobile apps)

    Alternative to callback redirect - returns JSON response
    """
    if "microsoft" not in oauth_service.get_available_providers():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Microsoft OAuth is not configured"
        )

    try:
        user, tokens = await oauth_service.handle_callback(
            provider="microsoft",
            code=code,
            state=state,
            db=db
        )

        return {
            "access_token": tokens["access_token"],
            "refresh_token": tokens["refresh_token"],
            "token_type": tokens["token_type"],
            "user": {
                "id": user.id,
                "email": user.email,
                "first_name": user.first_name,
                "last_name": user.last_name,
                "avatar_url": user.avatar_url,
                "role": user.role.value,
                "oauth_provider": user.oauth_provider
            }
        }

    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e)
        )
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"OAuth error: {str(e)}"
        )


@router.post("/unlink/{provider}", response_model=UnlinkProviderResponse)
async def unlink_oauth_provider(
    provider: str,
    db: AsyncSession = Depends(get_db),
    current_user: User = Depends(get_current_user)
):
    """
    Unlink OAuth provider from user account

    User must have a password set before unlinking
    """
    if provider not in ["google", "microsoft"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid OAuth provider"
        )

    try:
        success = await oauth_service.unlink_provider(db, current_user, provider)

        if not success:
            return {
                "success": False,
                "message": f"Account is not linked to {provider}"
            }

        return {
            "success": True,
            "message": f"Successfully unlinked {provider} account"
        }

    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(e)
        )


@router.get("/linked-accounts")
async def get_linked_accounts(
    current_user: User = Depends(get_current_user)
):
    """
    Get user's linked OAuth accounts
    """
    return {
        "linked_provider": current_user.oauth_provider,
        "has_password": bool(current_user.password_hash),
        "can_unlink": bool(current_user.password_hash) and bool(current_user.oauth_provider)
    }
