"""
Attachment processing service.
Handles images, videos (frame extraction), PDFs, and text files.
"""

import os
import io
import base64
import shutil
import subprocess
import tempfile
import mimetypes
from pathlib import Path
from uuid import uuid4
from typing import Optional

from backend import config

os.makedirs(config.ATTACHMENT_PATH, exist_ok=True)

IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff"}
VIDEO_EXTENSIONS = {".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".wmv", ".m4v"}
PDF_EXTENSIONS = {".pdf"}
TEXT_EXTENSIONS = {
    ".txt", ".md", ".py", ".js", ".ts", ".jsx", ".tsx", ".cs", ".java",
    ".cpp", ".c", ".h", ".hpp", ".go", ".rs", ".rb", ".php", ".swift",
    ".kt", ".lua", ".gd", ".html", ".css", ".scss", ".json", ".yaml",
    ".yml", ".xml", ".toml", ".ini", ".cfg", ".conf", ".sh", ".bash",
    ".sql", ".r", ".dart", ".vue", ".svelte", ".csv", ".log", ".env",
}

IMAGE_MIMES = {"image/jpeg", "image/png", "image/gif", "image/webp"}
VIDEO_MIMES = {"video/mp4", "video/quicktime", "video/x-msvideo", "video/webm"}


def classify_file(filename, mime):
    ext = Path(filename).suffix.lower()
    if ext in IMAGE_EXTENSIONS or mime in IMAGE_MIMES:
        return "image"
    if ext in VIDEO_EXTENSIONS or mime in VIDEO_MIMES:
        return "video"
    if ext in PDF_EXTENSIONS or mime == "application/pdf":
        return "document"
    return "text"


def get_mime_type(filename, content_type=None):
    if content_type and content_type != "application/octet-stream":
        return content_type
    mime, _ = mimetypes.guess_type(filename)
    return mime or "application/octet-stream"


def save_attachment(chat_id, filename, content, content_type=None):
    mime = get_mime_type(filename, content_type)
    file_type = classify_file(filename, mime)
    attachment_id = str(uuid4())

    chat_dir = os.path.join(config.ATTACHMENT_PATH, chat_id)
    os.makedirs(chat_dir, exist_ok=True)

    safe_name = Path(filename).name.replace(" ", "_")
    stored_name = f"{attachment_id}_{safe_name}"
    storage_path = os.path.join(chat_dir, stored_name)

    with open(storage_path, "wb") as f:
        f.write(content)

    text_extract = None
    if file_type == "text":
        text_extract = _extract_text_content(storage_path)
    elif file_type == "document":
        text_extract = _extract_pdf_text(storage_path)

    return {
        "id": attachment_id,
        "filename": stored_name,
        "original_filename": filename,
        "mime_type": mime,
        "file_type": file_type,
        "file_size": len(content),
        "storage_path": storage_path,
        "text_extract": text_extract,
    }


def delete_attachment_file(storage_path):
    try:
        if os.path.exists(storage_path):
            os.remove(storage_path)
    except Exception:
        pass


def delete_chat_attachments(chat_id):
    chat_dir = os.path.join(config.ATTACHMENT_PATH, chat_id)
    if os.path.isdir(chat_dir):
        shutil.rmtree(chat_dir, ignore_errors=True)


def build_claude_content_blocks(attachments):
    blocks = []
    for att in attachments:
        try:
            result = _process_single_attachment(att)
            if isinstance(result, list):
                blocks.extend(result)
            elif result:
                blocks.append(result)
        except Exception as e:
            blocks.append({
                "type": "text",
                "text": f"[Failed to process {att.original_filename}: {str(e)}]",
            })
    return blocks


def _process_single_attachment(att):
    if att.file_type == "image":
        return _build_image_block(att)
    elif att.file_type == "video":
        return _build_video_blocks(att)
    elif att.file_type == "document":
        return _build_document_block(att)
    elif att.file_type == "text":
        return _build_text_block(att)
    return None


def _build_image_block(att):
    data = _read_and_resize_image(att.storage_path, att.mime_type)
    mime = att.mime_type if att.mime_type in IMAGE_MIMES else "image/jpeg"
    return {
        "type": "image",
        "source": {"type": "base64", "media_type": mime, "data": data},
    }


def _build_video_blocks(att):
    frames = _extract_video_frames(att.storage_path)
    if not frames:
        return [{"type": "text", "text": f"[Video: {att.original_filename} - could not extract frames]"}]
    blocks = [{"type": "text", "text": f"[Video: {att.original_filename} - {len(frames)} frames extracted]"}]
    for frame_b64 in frames:
        blocks.append({
            "type": "image",
            "source": {"type": "base64", "media_type": "image/jpeg", "data": frame_b64},
        })
    return blocks


def _build_document_block(att):
    if att.mime_type == "application/pdf":
        with open(att.storage_path, "rb") as f:
            data = base64.b64encode(f.read()).decode("utf-8")
        return {
            "type": "document",
            "source": {"type": "base64", "media_type": "application/pdf", "data": data},
        }
    return _build_text_block(att)


def _build_text_block(att):
    text = att.text_extract or _extract_text_content(att.storage_path)
    if not text:
        text = f"[Could not extract text from {att.original_filename}]"
    return {
        "type": "text",
        "text": f"--- File: {att.original_filename} ---\n{text}\n--- End of {att.original_filename} ---",
    }


def _read_and_resize_image(path, mime_type):
    try:
        from PIL import Image
        img = Image.open(path)
        if img.mode in ("RGBA", "LA", "P"):
            bg = Image.new("RGB", img.size, (255, 255, 255))
            if img.mode == "P":
                img = img.convert("RGBA")
            bg.paste(img, mask=img.split()[-1] if "A" in img.mode else None)
            img = bg
        elif img.mode != "RGB":
            img = img.convert("RGB")
        mx = config.MAX_IMAGE_DIMENSION
        if img.width > mx or img.height > mx:
            ratio = min(mx / img.width, mx / img.height)
            img = img.resize((int(img.width * ratio), int(img.height * ratio)), Image.LANCZOS)
        buf = io.BytesIO()
        fmt = "PNG" if mime_type == "image/png" else "JPEG"
        kwargs = {"quality": 85} if fmt == "JPEG" else {}
        img.save(buf, format=fmt, **kwargs)
        return base64.b64encode(buf.getvalue()).decode("utf-8")
    except ImportError:
        with open(path, "rb") as f:
            return base64.b64encode(f.read()).decode("utf-8")
    except Exception:
        with open(path, "rb") as f:
            return base64.b64encode(f.read()).decode("utf-8")


def _extract_video_frames(video_path):
    if not shutil.which("ffmpeg") or not shutil.which("ffprobe"):
        return []
    frames = []
    try:
        result = subprocess.run(
            ["ffprobe", "-v", "error", "-show_entries", "format=duration",
             "-of", "default=noprint_wrappers=1:nokey=1", video_path],
            capture_output=True, text=True, timeout=30,
        )
        duration = float(result.stdout.strip() or "0")
        if duration <= 0:
            return []
        max_frames = config.MAX_VIDEO_FRAMES
        with tempfile.TemporaryDirectory() as tmpdir:
            interval = duration / (max_frames + 1)
            for i in range(max_frames):
                ts = interval * (i + 1)
                out = os.path.join(tmpdir, f"frame_{i}.jpg")
                subprocess.run(
                    ["ffmpeg", "-ss", str(ts), "-i", video_path, "-vframes", "1",
                     "-vf", f"scale='min({config.MAX_IMAGE_DIMENSION},iw)':'min({config.MAX_IMAGE_DIMENSION},ih)':force_original_aspect_ratio=decrease",
                     "-q:v", "3", out],
                    capture_output=True, timeout=30,
                )
                if os.path.exists(out) and os.path.getsize(out) > 0:
                    with open(out, "rb") as f:
                        frames.append(base64.b64encode(f.read()).decode("utf-8"))
    except Exception:
        pass
    return frames


def _extract_text_content(path):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read(500_000)
    except UnicodeDecodeError:
        try:
            with open(path, "r", encoding="latin-1") as f:
                return f.read(500_000)
        except Exception:
            return None
    except Exception:
        return None


def _extract_pdf_text(path):
    try:
        from PyPDF2 import PdfReader
        reader = PdfReader(path)
        pages = []
        for page in reader.pages[:100]:
            text = page.extract_text()
            if text:
                pages.append(text)
        return "\n\n".join(pages) if pages else None
    except Exception:
        return None
