"""
File Upload API Routes
Endpoints for file upload, download, and management
"""
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, Form, Query, BackgroundTasks
from fastapi.responses import StreamingResponse, JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel, Field
import uuid
import json
import logging
from io import BytesIO

from app.database import get_db
from app.models.file_upload import FileUpload, FileUploadBatch, FileStatus
from app.services.storage_service import (
    StorageService, StorageProvider, FileCategory, storage_service
)

logger = logging.getLogger(__name__)
router = APIRouter()


# ============================================================
# SCHEMAS
# ============================================================

class FileUploadResponse(BaseModel):
    """Response for single file upload"""
    id: int
    filename: str
    content_type: str
    size: int
    size_formatted: str
    url: str
    storage_provider: str
    category: str
    status: str
    created_at: datetime

    class Config:
        from_attributes = True


class PresignedUploadResponse(BaseModel):
    """Response for presigned upload URL"""
    upload_url: str
    fields: dict
    file_key: str
    expires_in: int


class BatchUploadResponse(BaseModel):
    """Response for batch upload"""
    batch_id: str
    total_files: int
    uploaded_files: int
    failed_files: int
    files: List[FileUploadResponse]
    errors: List[dict]


class FileListResponse(BaseModel):
    """Response for file listing"""
    files: List[FileUploadResponse]
    total: int
    page: int
    page_size: int
    has_more: bool


# ============================================================
# HELPER FUNCTIONS
# ============================================================

def get_category_from_content_type(content_type: str) -> FileCategory:
    """Determine file category from content type"""
    if content_type.startswith('image/'):
        return FileCategory.IMAGE
    elif content_type.startswith('application/pdf') or content_type.startswith('application/vnd.'):
        return FileCategory.DOCUMENT
    else:
        return FileCategory.ATTACHMENT


async def save_file_record(
    db: AsyncSession,
    metadata,
    client_id: Optional[int] = None,
    user_id: Optional[int] = None,
    category: str = "attachments",
    description: Optional[str] = None
) -> FileUpload:
    """Save file upload record to database"""
    file_record = FileUpload(
        client_id=client_id,
        original_filename=metadata.filename,
        stored_filename=metadata.key.split('/')[-1] if '/' in metadata.key else metadata.key,
        content_type=metadata.content_type,
        file_size=metadata.size,
        storage_provider=metadata.provider.value,
        storage_key=metadata.key,
        storage_url=metadata.url,
        category=category,
        checksum_md5=metadata.checksum,
        metadata_json=json.dumps(metadata.metadata) if metadata.metadata else None,
        status=FileStatus.UPLOADED,
        uploaded_by_user_id=user_id,
        description=description
    )

    db.add(file_record)
    await db.commit()
    await db.refresh(file_record)

    return file_record


# ============================================================
# SINGLE FILE UPLOAD
# ============================================================

@router.post("/upload", response_model=FileUploadResponse)
async def upload_file(
    file: UploadFile = File(...),
    category: str = Form(default="attachments"),
    client_id: Optional[int] = Form(default=None),
    description: Optional[str] = Form(default=None),
    public: bool = Form(default=False),
    db: AsyncSession = Depends(get_db)
):
    """
    Upload a single file

    - **file**: File to upload
    - **category**: File category (avatars, documents, images, attachments)
    - **client_id**: Optional client ID to associate file with
    - **description**: Optional file description
    - **public**: Whether file should be publicly accessible
    """
    try:
        # Read file content
        content = await file.read()

        if not content:
            raise HTTPException(status_code=400, detail="Empty file")

        # Determine category
        file_category = FileCategory(category) if category in [c.value for c in FileCategory] else FileCategory.ATTACHMENT

        # Upload to storage
        metadata = await storage_service.upload(
            file_data=content,
            filename=file.filename,
            content_type=file.content_type or 'application/octet-stream',
            category=file_category,
            client_id=client_id,
            public=public
        )

        # Save to database
        file_record = await save_file_record(
            db=db,
            metadata=metadata,
            client_id=client_id,
            category=category,
            description=description
        )

        return FileUploadResponse(
            id=file_record.id,
            filename=file_record.original_filename,
            content_type=file_record.content_type,
            size=file_record.file_size,
            size_formatted=file_record.size_formatted,
            url=file_record.storage_url,
            storage_provider=file_record.storage_provider,
            category=file_record.category,
            status=file_record.status.value,
            created_at=file_record.created_at
        )

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"Upload error: {e}")
        raise HTTPException(status_code=500, detail="Failed to upload file")


# ============================================================
# BATCH FILE UPLOAD
# ============================================================

@router.post("/upload/batch", response_model=BatchUploadResponse)
async def upload_batch(
    files: List[UploadFile] = File(...),
    category: str = Form(default="attachments"),
    client_id: Optional[int] = Form(default=None),
    public: bool = Form(default=False),
    db: AsyncSession = Depends(get_db)
):
    """
    Upload multiple files in a batch

    - **files**: List of files to upload (max 20)
    - **category**: File category for all files
    - **client_id**: Optional client ID
    - **public**: Whether files should be publicly accessible
    """
    if len(files) > 20:
        raise HTTPException(status_code=400, detail="Maximum 20 files per batch")

    batch_id = uuid.uuid4().hex

    # Create batch record
    batch = FileUploadBatch(
        client_id=client_id,
        batch_id=batch_id,
        total_files=len(files),
        status="uploading"
    )
    db.add(batch)
    await db.commit()

    uploaded_files = []
    errors = []
    file_category = FileCategory(category) if category in [c.value for c in FileCategory] else FileCategory.ATTACHMENT

    for idx, file in enumerate(files):
        try:
            content = await file.read()

            if not content:
                errors.append({"file": file.filename, "error": "Empty file"})
                continue

            # Upload to storage
            metadata = await storage_service.upload(
                file_data=content,
                filename=file.filename,
                content_type=file.content_type or 'application/octet-stream',
                category=file_category,
                client_id=client_id,
                public=public
            )

            # Save to database
            file_record = await save_file_record(
                db=db,
                metadata=metadata,
                client_id=client_id,
                category=category
            )

            uploaded_files.append(FileUploadResponse(
                id=file_record.id,
                filename=file_record.original_filename,
                content_type=file_record.content_type,
                size=file_record.file_size,
                size_formatted=file_record.size_formatted,
                url=file_record.storage_url,
                storage_provider=file_record.storage_provider,
                category=file_record.category,
                status=file_record.status.value,
                created_at=file_record.created_at
            ))

        except Exception as e:
            logger.error(f"Batch upload error for {file.filename}: {e}")
            errors.append({"file": file.filename, "error": str(e)})

    # Update batch record
    batch.uploaded_files = len(uploaded_files)
    batch.failed_files = len(errors)
    batch.status = "completed" if not errors else "completed_with_errors"
    batch.completed_at = datetime.utcnow()
    await db.commit()

    return BatchUploadResponse(
        batch_id=batch_id,
        total_files=len(files),
        uploaded_files=len(uploaded_files),
        failed_files=len(errors),
        files=uploaded_files,
        errors=errors
    )


# ============================================================
# PRESIGNED UPLOAD URL (Direct Browser Upload)
# ============================================================

@router.post("/upload/presigned", response_model=PresignedUploadResponse)
async def get_presigned_upload_url(
    filename: str = Form(...),
    content_type: str = Form(...),
    category: str = Form(default="attachments"),
    client_id: Optional[int] = Form(default=None)
):
    """
    Get a presigned URL for direct browser upload to S3

    This allows clients to upload directly to S3 without going through the server.

    - **filename**: Original filename
    - **content_type**: MIME type of the file
    - **category**: File category
    - **client_id**: Optional client ID
    """
    try:
        file_category = FileCategory(category) if category in [c.value for c in FileCategory] else FileCategory.ATTACHMENT

        result = await storage_service.get_presigned_upload_url(
            filename=filename,
            content_type=content_type,
            category=file_category,
            client_id=client_id,
            expiration=3600  # 1 hour
        )

        return PresignedUploadResponse(
            upload_url=result['url'],
            fields=result['fields'],
            file_key=result['fields']['key'],
            expires_in=3600
        )

    except Exception as e:
        logger.error(f"Presigned URL error: {e}")
        raise HTTPException(status_code=500, detail="Failed to generate upload URL")


@router.post("/upload/presigned/confirm")
async def confirm_presigned_upload(
    file_key: str = Form(...),
    filename: str = Form(...),
    content_type: str = Form(...),
    file_size: int = Form(...),
    category: str = Form(default="attachments"),
    client_id: Optional[int] = Form(default=None),
    db: AsyncSession = Depends(get_db)
):
    """
    Confirm a presigned upload after client uploads directly to S3

    Call this after successful direct upload to register the file in the database.
    """
    try:
        # Verify file exists in S3
        exists = await storage_service.s3.file_exists(file_key)
        if not exists:
            raise HTTPException(status_code=404, detail="File not found in storage")

        # Generate URL
        url = await storage_service.s3.generate_presigned_url(file_key)

        # Create file record
        file_record = FileUpload(
            client_id=client_id,
            original_filename=filename,
            stored_filename=file_key.split('/')[-1],
            content_type=content_type,
            file_size=file_size,
            storage_provider="s3",
            storage_key=file_key,
            storage_url=url,
            category=category,
            status=FileStatus.UPLOADED
        )

        db.add(file_record)
        await db.commit()
        await db.refresh(file_record)

        return FileUploadResponse(
            id=file_record.id,
            filename=file_record.original_filename,
            content_type=file_record.content_type,
            size=file_record.file_size,
            size_formatted=file_record.size_formatted,
            url=file_record.storage_url,
            storage_provider=file_record.storage_provider,
            category=file_record.category,
            status=file_record.status.value,
            created_at=file_record.created_at
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Confirm upload error: {e}")
        raise HTTPException(status_code=500, detail="Failed to confirm upload")


# ============================================================
# IMAGE UPLOAD WITH TRANSFORMATIONS (Cloudinary)
# ============================================================

@router.post("/upload/image")
async def upload_image(
    file: UploadFile = File(...),
    client_id: Optional[int] = Form(default=None),
    thumbnail: bool = Form(default=True),
    width: Optional[int] = Form(default=None),
    height: Optional[int] = Form(default=None),
    crop: str = Form(default="fill"),
    db: AsyncSession = Depends(get_db)
):
    """
    Upload an image with optional transformations (Cloudinary)

    - **file**: Image file to upload
    - **client_id**: Optional client ID
    - **thumbnail**: Generate thumbnail
    - **width**: Resize width
    - **height**: Resize height
    - **crop**: Crop mode (fill, fit, scale, crop, pad)
    """
    # Validate image
    if not file.content_type or not file.content_type.startswith('image/'):
        raise HTTPException(status_code=400, detail="File must be an image")

    try:
        content = await file.read()

        # Build transformations
        transformations = {}
        if width:
            transformations['width'] = width
        if height:
            transformations['height'] = height
        if width or height:
            transformations['crop'] = crop

        # Upload to Cloudinary
        metadata = await storage_service.cloudinary.upload_image(
            file_data=content,
            filename=file.filename,
            category=FileCategory.IMAGE,
            client_id=client_id,
            transformations=transformations if transformations else None
        )

        # Save to database
        file_record = await save_file_record(
            db=db,
            metadata=metadata,
            client_id=client_id,
            category="images"
        )

        # Build response with thumbnail URL if requested
        response = {
            "id": file_record.id,
            "filename": file_record.original_filename,
            "content_type": file_record.content_type,
            "size": file_record.file_size,
            "url": file_record.storage_url,
            "storage_provider": file_record.storage_provider,
            "status": file_record.status.value,
            "created_at": file_record.created_at.isoformat()
        }

        if thumbnail and file_record.storage_key:
            response["thumbnail_url"] = storage_service.cloudinary.get_thumbnail_url(
                file_record.storage_key, 150, 150
            )

        if metadata.metadata:
            response["dimensions"] = {
                "width": metadata.metadata.get("width"),
                "height": metadata.metadata.get("height")
            }

        return response

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"Image upload error: {e}")
        raise HTTPException(status_code=500, detail="Failed to upload image")


# ============================================================
# FILE DOWNLOAD
# ============================================================

@router.get("/files/{file_id}/download")
async def download_file(
    file_id: int,
    db: AsyncSession = Depends(get_db)
):
    """Download a file by ID"""
    result = await db.execute(
        select(FileUpload).where(
            and_(
                FileUpload.id == file_id,
                FileUpload.status != FileStatus.DELETED
            )
        )
    )
    file_record = result.scalar_one_or_none()

    if not file_record:
        raise HTTPException(status_code=404, detail="File not found")

    try:
        # Get file from storage
        provider = StorageProvider(file_record.storage_provider)
        data, content_type = await storage_service.download(
            file_record.storage_key,
            provider=provider
        )

        # Return as streaming response
        return StreamingResponse(
            BytesIO(data),
            media_type=content_type,
            headers={
                "Content-Disposition": f'attachment; filename="{file_record.original_filename}"',
                "Content-Length": str(len(data))
            }
        )

    except FileNotFoundError:
        raise HTTPException(status_code=404, detail="File not found in storage")
    except Exception as e:
        logger.error(f"Download error: {e}")
        raise HTTPException(status_code=500, detail="Failed to download file")


@router.get("/files/{file_id}/url")
async def get_file_url(
    file_id: int,
    expiration: int = Query(default=3600, ge=60, le=86400),
    db: AsyncSession = Depends(get_db)
):
    """Get temporary download URL for a file"""
    result = await db.execute(
        select(FileUpload).where(
            and_(
                FileUpload.id == file_id,
                FileUpload.status != FileStatus.DELETED
            )
        )
    )
    file_record = result.scalar_one_or_none()

    if not file_record:
        raise HTTPException(status_code=404, detail="File not found")

    try:
        provider = StorageProvider(file_record.storage_provider)
        url = await storage_service.get_download_url(
            file_record.storage_key,
            provider=provider,
            expiration=expiration
        )

        return {
            "url": url,
            "expires_in": expiration,
            "filename": file_record.original_filename
        }

    except Exception as e:
        logger.error(f"URL generation error: {e}")
        raise HTTPException(status_code=500, detail="Failed to generate URL")


# ============================================================
# FILE LISTING
# ============================================================

@router.get("/files", response_model=FileListResponse)
async def list_files(
    client_id: Optional[int] = Query(default=None),
    category: Optional[str] = Query(default=None),
    content_type: Optional[str] = Query(default=None),
    page: int = Query(default=1, ge=1),
    page_size: int = Query(default=20, ge=1, le=100),
    db: AsyncSession = Depends(get_db)
):
    """
    List uploaded files with filtering

    - **client_id**: Filter by client
    - **category**: Filter by category (avatars, documents, images, etc.)
    - **content_type**: Filter by content type prefix (image/, application/pdf)
    - **page**: Page number
    - **page_size**: Items per page
    """
    # Build query
    conditions = [FileUpload.status != FileStatus.DELETED]

    if client_id:
        conditions.append(FileUpload.client_id == client_id)
    if category:
        conditions.append(FileUpload.category == category)
    if content_type:
        conditions.append(FileUpload.content_type.like(f"{content_type}%"))

    # Get total count
    count_query = select(FileUpload).where(and_(*conditions))
    result = await db.execute(count_query)
    total = len(result.scalars().all())

    # Get paginated results
    query = (
        select(FileUpload)
        .where(and_(*conditions))
        .order_by(FileUpload.created_at.desc())
        .offset((page - 1) * page_size)
        .limit(page_size)
    )

    result = await db.execute(query)
    files = result.scalars().all()

    return FileListResponse(
        files=[
            FileUploadResponse(
                id=f.id,
                filename=f.original_filename,
                content_type=f.content_type,
                size=f.file_size,
                size_formatted=f.size_formatted,
                url=f.storage_url,
                storage_provider=f.storage_provider,
                category=f.category,
                status=f.status.value,
                created_at=f.created_at
            )
            for f in files
        ],
        total=total,
        page=page,
        page_size=page_size,
        has_more=(page * page_size) < total
    )


@router.get("/files/{file_id}")
async def get_file_info(
    file_id: int,
    db: AsyncSession = Depends(get_db)
):
    """Get file information by ID"""
    result = await db.execute(
        select(FileUpload).where(FileUpload.id == file_id)
    )
    file_record = result.scalar_one_or_none()

    if not file_record:
        raise HTTPException(status_code=404, detail="File not found")

    response = {
        "id": file_record.id,
        "filename": file_record.original_filename,
        "content_type": file_record.content_type,
        "size": file_record.file_size,
        "size_formatted": file_record.size_formatted,
        "url": file_record.storage_url,
        "storage_provider": file_record.storage_provider,
        "storage_key": file_record.storage_key,
        "category": file_record.category,
        "status": file_record.status.value,
        "checksum": file_record.checksum_md5,
        "description": file_record.description,
        "created_at": file_record.created_at.isoformat(),
        "updated_at": file_record.updated_at.isoformat() if file_record.updated_at else None,
        "is_image": file_record.is_image,
        "is_document": file_record.is_document
    }

    # Add metadata if available
    if file_record.metadata_json:
        try:
            response["metadata"] = json.loads(file_record.metadata_json)
        except:
            pass

    return response


# ============================================================
# FILE DELETION
# ============================================================

@router.delete("/files/{file_id}")
async def delete_file(
    file_id: int,
    permanent: bool = Query(default=False),
    db: AsyncSession = Depends(get_db)
):
    """
    Delete a file

    - **file_id**: File ID to delete
    - **permanent**: If true, permanently delete from storage. If false, soft delete.
    """
    result = await db.execute(
        select(FileUpload).where(FileUpload.id == file_id)
    )
    file_record = result.scalar_one_or_none()

    if not file_record:
        raise HTTPException(status_code=404, detail="File not found")

    if file_record.status == FileStatus.DELETED:
        raise HTTPException(status_code=400, detail="File already deleted")

    try:
        if permanent:
            # Delete from storage
            provider = StorageProvider(file_record.storage_provider)
            await storage_service.delete(
                file_record.storage_key,
                provider=provider
            )

            # Delete from database
            await db.delete(file_record)
        else:
            # Soft delete
            file_record.status = FileStatus.DELETED
            file_record.deleted_at = datetime.utcnow()

        await db.commit()

        return {
            "success": True,
            "message": "File deleted permanently" if permanent else "File deleted",
            "file_id": file_id
        }

    except Exception as e:
        logger.error(f"Delete error: {e}")
        raise HTTPException(status_code=500, detail="Failed to delete file")


@router.delete("/files/batch")
async def delete_files_batch(
    file_ids: List[int] = Query(...),
    permanent: bool = Query(default=False),
    db: AsyncSession = Depends(get_db)
):
    """Delete multiple files"""
    if len(file_ids) > 50:
        raise HTTPException(status_code=400, detail="Maximum 50 files per batch delete")

    deleted = []
    errors = []

    for file_id in file_ids:
        try:
            result = await db.execute(
                select(FileUpload).where(FileUpload.id == file_id)
            )
            file_record = result.scalar_one_or_none()

            if not file_record:
                errors.append({"file_id": file_id, "error": "Not found"})
                continue

            if file_record.status == FileStatus.DELETED:
                errors.append({"file_id": file_id, "error": "Already deleted"})
                continue

            if permanent:
                provider = StorageProvider(file_record.storage_provider)
                await storage_service.delete(file_record.storage_key, provider=provider)
                await db.delete(file_record)
            else:
                file_record.status = FileStatus.DELETED
                file_record.deleted_at = datetime.utcnow()

            deleted.append(file_id)

        except Exception as e:
            errors.append({"file_id": file_id, "error": str(e)})

    await db.commit()

    return {
        "deleted": deleted,
        "deleted_count": len(deleted),
        "errors": errors,
        "error_count": len(errors)
    }


# ============================================================
# AVATAR UPLOAD (Convenience endpoint)
# ============================================================

@router.post("/upload/avatar")
async def upload_avatar(
    file: UploadFile = File(...),
    client_id: Optional[int] = Form(default=None),
    user_id: Optional[int] = Form(default=None),
    db: AsyncSession = Depends(get_db)
):
    """
    Upload a user/client avatar image

    - Automatically resizes to standard avatar dimensions
    - Creates multiple sizes (small, medium, large)
    - Only accepts image files
    """
    if not file.content_type or not file.content_type.startswith('image/'):
        raise HTTPException(status_code=400, detail="Avatar must be an image file")

    try:
        content = await file.read()

        # Upload with avatar-specific transformations
        metadata = await storage_service.cloudinary.upload_image(
            file_data=content,
            filename=file.filename,
            category=FileCategory.AVATAR,
            client_id=client_id,
            transformations={
                'width': 400,
                'height': 400,
                'crop': 'fill',
                'gravity': 'face'  # Focus on face for avatars
            }
        )

        # Save to database
        file_record = await save_file_record(
            db=db,
            metadata=metadata,
            client_id=client_id,
            user_id=user_id,
            category="avatars"
        )

        # Generate different sizes
        public_id = file_record.storage_key
        urls = {
            "original": file_record.storage_url,
            "large": storage_service.cloudinary.get_thumbnail_url(public_id, 200, 200),
            "medium": storage_service.cloudinary.get_thumbnail_url(public_id, 100, 100),
            "small": storage_service.cloudinary.get_thumbnail_url(public_id, 50, 50)
        }

        return {
            "id": file_record.id,
            "filename": file_record.original_filename,
            "urls": urls,
            "storage_provider": file_record.storage_provider,
            "created_at": file_record.created_at.isoformat()
        }

    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"Avatar upload error: {e}")
        raise HTTPException(status_code=500, detail="Failed to upload avatar")