# Copyright (c) CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com
#

"""
V4 Documents API Client

This module provides functions for managing organization documents using the v4 API
with Okta authentication. All functions use the authenticated API client.
"""

from collections.abc import Callable
from pathlib import Path

import httpx
from pydantic import BaseModel, Field, ValidationError
from rich.console import Console

from src.api.authenticated import get_authenticated, make_authenticated_request, post_authenticated
from src.exceptions import APIError

console = Console()


# Pydantic Models for API Contract (matching backend)
class DocumentInfo(BaseModel):
    """Document information model."""

    key: str = Field(..., description="S3 object key")
    filename: str = Field(..., description="Original filename")
    size: int = Field(..., description="File size in bytes")
    last_modified: str = Field(..., description="Last modification timestamp")  # ISO format string
    etag: str = Field(..., description="S3 ETag")


class DocumentsListResponse(BaseModel):
    """Response model for listing documents."""

    success: bool = Field(True, description="Operation success status")
    organization_id: str = Field(..., description="Organization ID")
    documents: list[DocumentInfo] = Field(default_factory=list, description="List of documents")
    count: int = Field(..., description="Number of documents")


class DocumentUploadRequest(BaseModel):
    """Request model for document upload."""

    filename: str = Field(..., description="Original filename", min_length=1, max_length=255)
    content_type: str | None = Field(None, description="MIME content type")
    metadata: dict[str, str] | None = Field(default_factory=dict, description="Additional metadata")


class DocumentUploadResponse(BaseModel):
    """Response model for document upload."""

    success: bool = Field(True, description="Operation success status")
    organization_id: str = Field(..., description="Organization ID")
    upload_url: str = Field(..., description="Pre-signed upload URL")
    fields: dict[str, str] = Field(..., description="Form fields for upload")
    expires_in: int = Field(3600, description="URL expiration time in seconds")


class DocumentDownloadResponse(BaseModel):
    """Response model for document download."""

    success: bool = Field(True, description="Operation success status")
    organization_id: str = Field(..., description="Organization ID")
    filename: str = Field(..., description="Document filename")
    download_url: str = Field(..., description="Pre-signed download URL")
    expires_in: int = Field(3600, description="URL expiration time in seconds")


class DocumentDeleteResponse(BaseModel):
    """Response model for document deletion."""

    success: bool = Field(True, description="Operation success status")
    organization_id: str = Field(..., description="Organization ID")
    filename: str = Field(..., description="Deleted filename")
    message: str = Field(..., description="Success message")


def list_documents(organization_id: str, debug: bool = False) -> list[DocumentInfo]:
    """
    List documents for an organization.

    Args:
        organization_id: CloudZero organization ID
        debug: Whether to print debug information

    Returns:
        List of DocumentInfo objects

    Raises:
        APIError: If the request fails
        ValidationError: If the response format is invalid
    """
    try:
        response_data = get_authenticated(endpoint_path=f"documents/{organization_id}", debug=debug)

        # Validate response using Pydantic
        response = DocumentsListResponse(**response_data)
        return response.documents

    except ValidationError as e:
        raise APIError(f"Invalid response format from documents API: {e}") from e
    except Exception as e:
        raise APIError(f"Failed to list documents for organization {organization_id}: {e}") from e


def generate_upload_url(
    organization_id: str,
    filename: str,
    content_type: str | None = None,
    metadata: dict[str, str] | None = None,
    debug: bool = False,
) -> DocumentUploadResponse:
    """
    Generate pre-signed URL for document upload.

    Args:
        organization_id: CloudZero organization ID
        filename: Document filename
        content_type: MIME content type (optional)
        metadata: Additional metadata (optional)
        debug: Whether to print debug information

    Returns:
        DocumentUploadResponse with upload URL and fields

    Raises:
        APIError: If the request fails
        ValidationError: If the response format is invalid
    """
    try:
        # Prepare request data
        request_data = DocumentUploadRequest(filename=filename, content_type=content_type, metadata=metadata or {})

        response_data = post_authenticated(
            endpoint_path=f"documents/{organization_id}/upload",
            json_data=request_data.model_dump(exclude_unset=True),
            debug=debug,
        )

        # Validate response using Pydantic
        response = DocumentUploadResponse(**response_data)
        return response

    except ValidationError as e:
        raise APIError(f"Invalid response format from upload URL API: {e}") from e
    except Exception as e:
        raise APIError(f"Failed to generate upload URL for {filename} in organization {organization_id}: {e}") from e


def upload_file(
    organization_id: str, file_path: str, progress_callback: Callable | None = None, debug: bool = False
) -> bool:
    """
    Upload a file to organization document storage.

    Args:
        organization_id: CloudZero organization ID
        file_path: Path to file to upload
        progress_callback: Optional callback for upload progress (called with bytes_uploaded)
        debug: Whether to print debug information

    Returns:
        True if upload successful

    Raises:
        APIError: If the upload fails
        FileNotFoundError: If the file doesn't exist
    """
    file_path_obj = Path(file_path)

    if not file_path_obj.exists():
        raise FileNotFoundError(f"File not found: {file_path}")

    if not file_path_obj.is_file():
        raise APIError(f"Path is not a file: {file_path}")

    filename = file_path_obj.name
    file_size = file_path_obj.stat().st_size

    try:
        # Get upload URL
        upload_response = generate_upload_url(organization_id=organization_id, filename=filename, debug=debug)

        # Upload using httpx (the file is opened in the with-block below)
        with httpx.Client(timeout=300) as client:  # 5 minute timeout for uploads
            # Create form data with required fields
            data = upload_response.fields.copy()

            if debug:
                console.print(f"\n[yellow]DEBUG: Uploading to URL[/yellow]: {upload_response.upload_url}")
                console.print(f"[yellow]DEBUG: Form fields[/yellow]: {data}")

            # Open file and track progress
            with open(file_path, "rb") as f:
                files = {"file": (filename, f)}

                # Simple upload without progress tracking for now
                # TODO: Add proper progress tracking for large files
                response = client.post(upload_response.upload_url, data=data, files=files)

                response.raise_for_status()

                if progress_callback:
                    progress_callback(file_size)

                if debug:
                    console.print("[green]DEBUG: Upload completed successfully[/green]")
                    console.print(f"[yellow]DEBUG: Response status[/yellow]: {response.status_code}")

        return True

    except httpx.HTTPStatusError as e:
        raise APIError(f"Upload failed with status {e.response.status_code}: {e.response.text}") from e
    except Exception as e:
        raise APIError(f"Failed to upload file {filename} to organization {organization_id}: {e}") from e


def generate_download_url(organization_id: str, filename: str, debug: bool = False) -> DocumentDownloadResponse:
    """
    Generate pre-signed URL for document download.

    Args:
        organization_id: CloudZero organization ID
        filename: Document filename
        debug: Whether to print debug information

    Returns:
        DocumentDownloadResponse with download URL

    Raises:
        APIError: If the request fails
        ValidationError: If the response format is invalid
    """
    try:
        response_data = get_authenticated(endpoint_path=f"documents/{organization_id}/{filename}", debug=debug)

        # Validate response using Pydantic
        response = DocumentDownloadResponse(**response_data)
        return response

    except ValidationError as e:
        raise APIError(f"Invalid response format from download URL API: {e}") from e
    except Exception as e:
        raise APIError(f"Failed to generate download URL for {filename} in organization {organization_id}: {e}") from e


def download_file(organization_id: str, filename: str, destination_path: str | None = None, debug: bool = False) -> str:
    """
    Download a file from organization document storage.

    Args:
        organization_id: CloudZero organization ID
        filename: Document filename to download
        destination_path: Optional destination directory (defaults to current directory)
        debug: Whether to print debug information

    Returns:
        Path to downloaded file

    Raises:
        APIError: If the download fails
    """
    try:
        # Get download URL
        download_response = generate_download_url(organization_id=organization_id, filename=filename, debug=debug)

        # Determine destination path
        if destination_path:
            dest_dir = Path(destination_path)
            dest_dir.mkdir(parents=True, exist_ok=True)
            local_file_path = dest_dir / filename
        else:
            local_file_path = Path(filename)

        if debug:
            console.print(f"\n[yellow]DEBUG: Downloading from URL[/yellow]: {download_response.download_url}")
            console.print(f"[yellow]DEBUG: Saving to[/yellow]: {local_file_path}")

        # Download the file
        with httpx.Client(timeout=300) as client, client.stream("GET", download_response.download_url) as response:
            response.raise_for_status()

            with open(local_file_path, "wb") as f:
                for chunk in response.iter_bytes():
                    f.write(chunk)

        if debug:
            console.print("[green]DEBUG: Download completed successfully[/green]")

        return str(local_file_path)

    except httpx.HTTPStatusError as e:
        raise APIError(f"Download failed with status {e.response.status_code}: {e.response.text}") from e
    except Exception as e:
        raise APIError(f"Failed to download file {filename} from organization {organization_id}: {e}") from e


def delete_document(organization_id: str, filename: str, debug: bool = False) -> bool:
    """
    Delete a document from organization storage.

    Args:
        organization_id: CloudZero organization ID
        filename: Document filename to delete
        debug: Whether to print debug information

    Returns:
        True if deletion successful

    Raises:
        APIError: If the deletion fails
        ValidationError: If the response format is invalid
    """
    try:
        response = make_authenticated_request(
            method="DELETE", endpoint_path=f"documents/{organization_id}/{filename}", debug=debug
        )

        response.raise_for_status()
        response_data = response.json()

        # Validate response using Pydantic
        delete_response = DocumentDeleteResponse(**response_data)

        if debug:
            console.print(f"[green]DEBUG: {delete_response.message}[/green]")

        return True

    except ValidationError as e:
        raise APIError(f"Invalid response format from delete API: {e}") from e
    except httpx.HTTPStatusError as e:
        if e.response.status_code == 404:
            raise APIError(f"Document '{filename}' not found in organization {organization_id}") from e
        raise APIError(f"Delete failed with status {e.response.status_code}: {e.response.text}") from e
    except Exception as e:
        raise APIError(f"Failed to delete document {filename} from organization {organization_id}: {e}") from e
