"""
OneDrive API endpoints
"""

import logging
from datetime import datetime, timedelta
from typing import Annotated, Optional

import httpx
import requests
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
from sqlalchemy.orm import Session

from app.auth import require_login
from app.config import settings
from app.database import get_db
from app.utils.env_utils import update_env_file
from app.utils.oauth_helper import exchange_oauth_token
from app.utils.settings_service import save_setting_to_db
from app.utils.settings_sync import notify_settings_updated

# Set up logging
logger = logging.getLogger(__name__)

router = APIRouter()


@router.post("/onedrive/exchange-token")
@require_login
async def exchange_onedrive_token(
    request: Request,
    client_id: Annotated[str, Form(...)],
    client_secret: Annotated[str, Form(...)],
    redirect_uri: Annotated[str, Form(...)],
    code: Annotated[str, Form(...)],
    tenant_id: Annotated[str, Form(...)],
):
    """
    Exchange an authorization code for a refresh token.
    This is done on the server to avoid exposing client secret in the browser.
    """
    # Prepare the token request
    token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"

    payload = {
        "client_id": client_id,
        "scope": "https://graph.microsoft.com/.default offline_access",
        "code": code,
        "redirect_uri": redirect_uri,
        "grant_type": "authorization_code",
        "client_secret": client_secret,
    }

    # Use shared OAuth helper (handles secure logging and error handling)
    token_data = exchange_oauth_token(provider_name="OneDrive", token_url=token_url, payload=payload)

    # Return just what's needed by the frontend
    return {
        "refresh_token": token_data["refresh_token"],
        "access_token": token_data.get("access_token", ""),
        "expires_in": token_data.get("expires_in", 3600),
    }


@router.get("/onedrive/test-token")
@require_login
async def test_onedrive_token(request: Request):
    """
    Test if the configured OneDrive token is valid and return expiration information.
    """
    try:
        logger.info("Testing OneDrive token validity")

        if (
            not settings.onedrive_refresh_token
            or not settings.onedrive_client_id
            or not settings.onedrive_client_secret
        ):
            logger.warning("OneDrive credentials not fully configured")
            return {
                "status": "error",
                "message": "OneDrive credentials are not fully configured",
            }

        # Refresh token to get a new access token and expiration info
        tenant_id = settings.onedrive_tenant_id or "common"
        token_url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"

        refresh_data = {
            "client_id": settings.onedrive_client_id,
            "client_secret": settings.onedrive_client_secret,
            "refresh_token": settings.onedrive_refresh_token,
            "grant_type": "refresh_token",
            "scope": "offline_access Files.ReadWrite",
        }

        async with httpx.AsyncClient(timeout=settings.http_request_timeout) as client:
            response = await client.post(token_url, data=refresh_data)

            if response.status_code != 200:
                logger.error(f"Failed to refresh OneDrive token: {response.text}")
                return {
                    "status": "error",
                    "message": "Refresh token has expired or is invalid",
                    "needs_reauth": True,
                }

            token_data = response.json()
        access_token = token_data.get("access_token")
        expires_in = token_data.get("expires_in", 3600)  # Default to 1 hour if not specified

        # Check if we got a new refresh token (Microsoft sometimes issues a new one)
        new_refresh_token = token_data.get("refresh_token")
        if new_refresh_token and new_refresh_token != settings.onedrive_refresh_token:
            logger.info("Received new refresh token from Microsoft - will update configuration")

            # Update refresh token in memory
            settings.onedrive_refresh_token = new_refresh_token

            # Also try to update .env file if it exists
            update_env_file({"ONEDRIVE_REFRESH_TOKEN": new_refresh_token})

            # Persist the rotated refresh token to the database
            try:
                from app.database import SessionLocal

                _db = SessionLocal()
                try:
                    save_setting_to_db(
                        _db,
                        "onedrive_refresh_token",
                        new_refresh_token,
                        changed_by="onedrive_token_rotation",
                    )
                    notify_settings_updated()
                finally:
                    _db.close()
            except Exception as _e:
                logger.warning(f"Failed to persist rotated OneDrive refresh token to database: {_e}")

        # Test the access token by getting user information
        user_info_url = "https://graph.microsoft.com/v1.0/me"
        headers = {"Authorization": f"Bearer {access_token}"}

        async with httpx.AsyncClient(timeout=settings.http_request_timeout) as client:
            user_response = await client.get(user_info_url, headers=headers)

            if user_response.status_code != 200:
                logger.error(f"OneDrive token test failed: {user_response.status_code} {user_response.text}")
                return {
                    "status": "error",
                    "message": f"Token validation failed with status {user_response.status_code}: {user_response.text}",
                }

            # Get user info
            user_info = user_response.json()
        display_name = user_info.get("displayName", "Unknown user")
        email = user_info.get("userPrincipalName", "Unknown email")

        # Calculate expiration time
        now = datetime.now()
        expiry_time = now + timedelta(seconds=expires_in)

        # Format expiration info
        time_left = expiry_time - now
        token_info = {
            "expires_at": expiry_time.isoformat(),
            "expires_in_seconds": expires_in,
            "expires_in_human": format_time_remaining(time_left),
            "refresh_token_validity": "Refresh token is valid for 90 days of inactivity",
        }

        logger.info(f"Successfully connected to OneDrive as {email}")

        return {
            "status": "success",
            "message": "OneDrive connection successful",
            "account": email,
            "account_name": display_name,
            "token_info": token_info,
        }

    except Exception as e:
        logger.exception(f"Unexpected error testing OneDrive token: {str(e)}")
        return {"status": "error", "message": f"Connection error: {str(e)}"}


@router.post("/onedrive/list-folders")
@require_login
async def list_onedrive_folders(
    request: Request,
    access_token: Annotated[str, Form(...)],
    path: Annotated[str, Form()] = "",
):
    """
    List folders in a OneDrive account for the directory selector.

    Accepts an OAuth access token (short-lived) and a path to list.
    Returns a flat list of folder entries under the given path.
    """
    try:
        folder_path = path.strip().strip("/")

        headers = {
            "Authorization": f"Bearer {access_token}",
        }

        # Build the Graph API URL for listing children
        if not folder_path or folder_path == "root":
            url = "https://graph.microsoft.com/v1.0/me/drive/root/children"
        else:
            url = f"https://graph.microsoft.com/v1.0/me/drive/root:/{folder_path}:/children"

        # Only request folders and minimal fields
        params = {
            "$filter": "folder ne null",
            "$select": "name,id,parentReference,folder",
            "$top": "200",
        }

        response = requests.get(
            url,
            headers=headers,
            params=params,
            timeout=settings.http_request_timeout,
        )

        if response.status_code == 401:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Access token is invalid or expired. Please re-authorize.",
            )

        if response.status_code != 200:
            logger.error(f"OneDrive list children failed: {response.status_code} {response.text}")
            raise HTTPException(
                status_code=status.HTTP_502_BAD_GATEWAY,
                detail=f"Failed to list OneDrive folders: {response.text}",
            )

        data = response.json()
        folders = []
        for item in data.get("value", []):
            if "folder" in item:
                parent_path = ""
                if item.get("parentReference", {}).get("path"):
                    # parentReference.path looks like /drive/root:/some/path
                    raw_parent = item["parentReference"]["path"]
                    prefix = "/drive/root:"
                    if raw_parent.startswith(prefix):
                        parent_path = raw_parent[len(prefix) :]
                    elif raw_parent == "/drive/root":
                        parent_path = ""

                item_path = f"{parent_path}/{item['name']}" if parent_path else f"/{item['name']}"

                folders.append(
                    {
                        "name": item["name"],
                        "path": item_path,
                        "id": item.get("id", ""),
                        "child_count": item.get("folder", {}).get("childCount", 0),
                    }
                )

        # Sort folders alphabetically
        folders.sort(key=lambda f: f["name"].lower())

        return {
            "folders": folders,
            "path": f"/{folder_path}" if folder_path else "/",
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.exception(f"Error listing OneDrive folders: {e}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to list folders: {str(e)}",
        )


def format_time_remaining(time_delta):
    """Format a timedelta into a human-readable string."""
    if time_delta.total_seconds() <= 0:
        return "Expired"

    days = time_delta.days
    hours, remainder = divmod(time_delta.seconds, 3600)
    minutes, seconds = divmod(remainder, 60)

    parts = []
    if days > 0:
        parts.append(f"{days} day{'s' if days != 1 else ''}")
    if hours > 0:
        parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
    if minutes > 0 and days == 0:  # Only show minutes if less than a day
        parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")

    return ", ".join(parts)


@router.post("/onedrive/save-settings")
@require_login
async def save_onedrive_settings(
    request: Request,
    refresh_token: Annotated[str, Form(...)],
    client_id: Annotated[Optional[str], Form()] = None,
    client_secret: Annotated[Optional[str], Form()] = None,
    tenant_id: Annotated[str, Form()] = "common",
    folder_path: Annotated[Optional[str], Form()] = None,
    db: Session = Depends(get_db),
):
    """
    Saves to database (primary) and .env file (best-effort).
    """
    try:
        user = request.session.get("user", {}) if hasattr(request, "session") else {}
        changed_by = (
            user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
        )

        # Build settings dictionary mapped to database/memory keys
        onedrive_settings = {
            "onedrive_refresh_token": refresh_token,
            "onedrive_client_id": client_id,
            "onedrive_client_secret": client_secret,
            "onedrive_tenant_id": tenant_id,
            "onedrive_folder_path": folder_path,
        }

        # Filter out None values
        onedrive_settings = {k: v for k, v in onedrive_settings.items() if v is not None}

        # Best-effort .env file write using the new utility
        env_settings = {k.upper(): v for k, v in onedrive_settings.items()}
        update_env_file(env_settings)

        # Update in-memory settings and persist to database dynamically
        for key, value in onedrive_settings.items():
            setattr(settings, key, value)
            save_setting_to_db(db, key, value, changed_by=changed_by)

        notify_settings_updated()

        logger.info("Successfully saved OneDrive settings")
        return {"status": "success", "message": "OneDrive settings have been saved"}

    except Exception as e:
        logger.exception(f"Unexpected error saving OneDrive settings: {str(e)}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to save OneDrive settings: {str(e)}",
        )


@router.post("/onedrive/update-settings")
@require_login
async def update_onedrive_settings(
    request: Request,
    refresh_token: Annotated[str, Form(...)],
    client_id: Annotated[Optional[str], Form()] = None,
    client_secret: Annotated[Optional[str], Form()] = None,
    tenant_id: Annotated[str, Form()] = "common",
    folder_path: Annotated[Optional[str], Form()] = None,
    db: Session = Depends(get_db),
):
    """
    Update OneDrive settings in memory and persist to database
    """
    try:
        logger.info("Updating OneDrive settings in memory and database")

        user = request.session.get("user", {}) if hasattr(request, "session") else {}
        changed_by = (
            user.get("preferred_username") or user.get("username") or user.get("email") or user.get("id") or "wizard"
        )

        # Update settings in memory and persist to database
        if refresh_token:
            settings.onedrive_refresh_token = refresh_token
            save_setting_to_db(db, "onedrive_refresh_token", refresh_token, changed_by=changed_by)
            logger.info("Updated ONEDRIVE_REFRESH_TOKEN in memory and database")

        if client_id:
            settings.onedrive_client_id = client_id
            save_setting_to_db(db, "onedrive_client_id", client_id, changed_by=changed_by)
            logger.info("Updated ONEDRIVE_CLIENT_ID in memory and database")

        if client_secret:
            settings.onedrive_client_secret = client_secret
            save_setting_to_db(db, "onedrive_client_secret", client_secret, changed_by=changed_by)
            logger.info("Updated ONEDRIVE_CLIENT_SECRET in memory and database")

        if tenant_id:
            settings.onedrive_tenant_id = tenant_id
            save_setting_to_db(db, "onedrive_tenant_id", tenant_id, changed_by=changed_by)
            logger.info("Updated ONEDRIVE_TENANT_ID in memory and database")

        if folder_path:
            settings.onedrive_folder_path = folder_path
            save_setting_to_db(db, "onedrive_folder_path", folder_path, changed_by=changed_by)
            logger.info("Updated ONEDRIVE_FOLDER_PATH in memory and database")

        notify_settings_updated()

        # Test the token to make sure it works
        try:
            from app.tasks.upload_to_onedrive import get_onedrive_token

            get_onedrive_token()  # Test that token can be retrieved
            logger.info("Successfully tested OneDrive token")
        except Exception as e:
            logger.error(f"Token test failed after updating settings: {str(e)}")
            return {
                "status": "warning",
                "message": "Settings updated but token test failed: " + str(e),
            }

        return {
            "status": "success",
            "message": "OneDrive settings have been updated in memory and database",
        }

    except Exception as e:
        logger.exception(f"Unexpected error updating OneDrive settings: {str(e)}")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to update OneDrive settings: {str(e)}",
        )


@router.get("/onedrive/get-full-config")
@require_login
async def get_onedrive_full_config(request: Request):
    """
    Get the full OneDrive configuration for sharing with worker nodes
    """
    try:
        # Create a configuration object with all OneDrive settings
        config = {
            "client_id": settings.onedrive_client_id or "",
            "client_secret": settings.onedrive_client_secret or "",
            "tenant_id": settings.onedrive_tenant_id or "common",
            "refresh_token": settings.onedrive_refresh_token or "",
            "folder_path": settings.onedrive_folder_path or "Documents/Uploads",
        }

        # Generate environment variable format
        env_format = "\n".join(
            [
                f"ONEDRIVE_CLIENT_ID={config['client_id']}",
                f"ONEDRIVE_CLIENT_SECRET={config['client_secret']}",
                f"ONEDRIVE_TENANT_ID={config['tenant_id']}",
                f"ONEDRIVE_REFRESH_TOKEN={config['refresh_token']}",
                f"ONEDRIVE_FOLDER_PATH={config['folder_path']}",
            ]
        )

        return {"status": "success", "config": config, "env_format": env_format}
    except Exception as e:
        logger.exception("Error getting OneDrive configuration")
        return {"status": "error", "message": str(e)}
