"""CRUD operations for receipt attachments."""

import os
import uuid
import aiofiles
from datetime import datetime
from pathlib import Path
from typing import Optional, List

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import UploadFile

from backend.modules.data_entry.db.models.receipt import ReceiptAttachment
from backend.config import settings


class AttachmentCRUD:
    """CRUD operations for ReceiptAttachment model."""

    @staticmethod
    def _generate_stored_filename(original_filename: str) -> str:
        """Generate unique filename for storage."""
        ext = Path(original_filename).suffix.lower()
        return f"{uuid.uuid4()}{ext}"

    @staticmethod
    def _get_upload_path(stored_filename: str) -> Path:
        """Get full path for storing file, organized by year/month."""
        now = datetime.utcnow()
        relative_path = Path(str(now.year)) / f"{now.month:02d}"
        full_path = settings.data_entry_upload_path_resolved / relative_path

        # Ensure directory exists
        full_path.mkdir(parents=True, exist_ok=True)

        return relative_path / stored_filename

    @staticmethod
    async def create(
        session: AsyncSession,
        receipt_id: int,
        file: UploadFile,
    ) -> ReceiptAttachment:
        """Create attachment by saving file and creating DB record."""
        # Generate stored filename
        stored_filename = AttachmentCRUD._generate_stored_filename(file.filename or "upload")

        # Get relative path
        relative_path = AttachmentCRUD._get_upload_path(stored_filename)

        # Full path for saving
        full_path = settings.data_entry_upload_path_resolved / relative_path

        # Read file content
        content = await file.read()
        file_size = len(content)

        # Validate file size
        if file_size > settings.data_entry_max_upload_size_bytes:
            raise ValueError(f"File too large. Maximum size is {settings.data_entry_max_upload_size_mb}MB")

        # Validate MIME type
        mime_type = file.content_type or "application/octet-stream"
        if mime_type not in settings.data_entry_allowed_mime_types:
            raise ValueError(f"File type not allowed: {mime_type}")

        # Save file
        async with aiofiles.open(full_path, "wb") as f:
            await f.write(content)

        # Create DB record
        attachment = ReceiptAttachment(
            receipt_id=receipt_id,
            filename=file.filename or "upload",
            stored_filename=stored_filename,
            file_path=str(relative_path),
            file_size=file_size,
            mime_type=mime_type,
        )

        session.add(attachment)
        await session.commit()
        await session.refresh(attachment)

        return attachment

    @staticmethod
    async def get_by_id(
        session: AsyncSession,
        attachment_id: int,
    ) -> Optional[ReceiptAttachment]:
        """Get attachment by ID."""
        query = select(ReceiptAttachment).where(ReceiptAttachment.id == attachment_id)
        result = await session.execute(query)
        return result.scalar_one_or_none()

    @staticmethod
    async def get_by_receipt_id(
        session: AsyncSession,
        receipt_id: int,
    ) -> List[ReceiptAttachment]:
        """Get all attachments for a receipt."""
        query = select(ReceiptAttachment).where(
            ReceiptAttachment.receipt_id == receipt_id
        ).order_by(ReceiptAttachment.uploaded_at.asc())

        result = await session.execute(query)
        return list(result.scalars().all())

    @staticmethod
    def get_file_path(attachment: ReceiptAttachment) -> Path:
        """Get full file path for an attachment."""
        return settings.data_entry_upload_path_resolved / attachment.file_path

    @staticmethod
    async def delete(session: AsyncSession, attachment: ReceiptAttachment) -> bool:
        """Delete attachment (file and DB record)."""
        # Delete file
        file_path = AttachmentCRUD.get_file_path(attachment)
        if file_path.exists():
            os.remove(file_path)

        # Delete DB record
        await session.delete(attachment)
        await session.commit()

        return True

    @staticmethod
    async def delete_all_for_receipt(session: AsyncSession, receipt_id: int) -> int:
        """Delete all attachments for a receipt."""
        attachments = await AttachmentCRUD.get_by_receipt_id(session, receipt_id)
        count = 0

        for attachment in attachments:
            await AttachmentCRUD.delete(session, attachment)
            count += 1

        return count
