"""
Document Type Detector Service

Intelligently determines the optimal processing track for documents based on
file type, content analysis, and editability checks.
"""

import os
import logging
import magic
import fitz  # PyMuPDF
from pathlib import Path
from typing import Dict, Optional, Tuple, List
from enum import Enum
import statistics
import tempfile

from app.services.office_converter import OfficeConverter, OfficeConverterError

logger = logging.getLogger(__name__)


class DocumentType(str, Enum):
    """Document type classification"""
    PDF_EDITABLE = "pdf_editable"      # PDF with extractable text
    PDF_SCANNED = "pdf_scanned"        # PDF with images/scanned content
    PDF_MIXED = "pdf_mixed"            # PDF with both text and scanned pages
    IMAGE = "image"                     # Image files (PNG, JPG, etc.)
    OFFICE_WORD = "office_word"        # Word documents
    OFFICE_EXCEL = "office_excel"      # Excel spreadsheets
    OFFICE_POWERPOINT = "office_ppt"   # PowerPoint presentations
    TEXT = "text"                       # Plain text files
    UNKNOWN = "unknown"                 # Unknown format


class ProcessingTrackRecommendation:
    """Processing track recommendation with confidence"""

    def __init__(self,
                 track: str,
                 confidence: float,
                 reason: str,
                 document_type: DocumentType,
                 metadata: Optional[Dict] = None):
        self.track = track  # "ocr" or "direct"
        self.confidence = confidence  # 0.0 to 1.0
        self.reason = reason
        self.document_type = document_type
        self.metadata = metadata or {}

    def to_dict(self) -> Dict:
        return {
            "recommended_track": self.track,
            "confidence": self.confidence,
            "reason": self.reason,
            "document_type": self.document_type.value,
            "metadata": self.metadata
        }


class DocumentTypeDetector:
    """
    Service for detecting document types and recommending processing tracks.

    This service analyzes documents to determine:
    1. The document type (PDF, image, Office, etc.)
    2. Whether the document contains extractable text
    3. The recommended processing track (OCR vs Direct)
    """

    # MIME type mappings
    IMAGE_MIMES = {
        'image/png', 'image/jpeg', 'image/jpg', 'image/gif',
        'image/bmp', 'image/tiff', 'image/webp'
    }

    OFFICE_MIMES = {
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document': DocumentType.OFFICE_WORD,
        'application/msword': DocumentType.OFFICE_WORD,
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': DocumentType.OFFICE_EXCEL,
        'application/vnd.ms-excel': DocumentType.OFFICE_EXCEL,
        'application/vnd.openxmlformats-officedocument.presentationml.presentation': DocumentType.OFFICE_POWERPOINT,
        'application/vnd.ms-powerpoint': DocumentType.OFFICE_POWERPOINT,
    }

    def __init__(self,
                 min_text_length: int = 100,
                 sample_pages: int = 3,
                 text_coverage_threshold: float = 0.9):
        """
        Initialize the detector.

        Args:
            min_text_length: Minimum text length to consider a page as having extractable text
            sample_pages: Number of pages to sample for PDF analysis
            text_coverage_threshold: Percentage of pages with text to classify as editable
        """
        self.min_text_length = min_text_length
        self.sample_pages = sample_pages
        self.text_coverage_threshold = text_coverage_threshold

    def detect(self, file_path: Path) -> ProcessingTrackRecommendation:
        """
        Detect document type and recommend processing track.

        Args:
            file_path: Path to the document file

        Returns:
            ProcessingTrackRecommendation with track selection and metadata
        """
        if not file_path.exists():
            logger.error(f"File not found: {file_path}")
            return ProcessingTrackRecommendation(
                track="ocr",
                confidence=0.5,
                reason="File not found, defaulting to OCR",
                document_type=DocumentType.UNKNOWN
            )

        try:
            # Detect MIME type
            mime_type = magic.from_file(str(file_path), mime=True)
            logger.info(f"Detected MIME type: {mime_type} for {file_path.name}")

            # Route based on file type
            if mime_type == 'application/pdf':
                return self._analyze_pdf(file_path)
            elif mime_type in self.IMAGE_MIMES:
                return self._analyze_image(file_path, mime_type)
            elif mime_type in self.OFFICE_MIMES:
                return self._analyze_office(file_path, mime_type)
            elif mime_type.startswith('text/'):
                return self._analyze_text(file_path, mime_type)
            else:
                logger.warning(f"Unknown MIME type: {mime_type}")
                return ProcessingTrackRecommendation(
                    track="ocr",
                    confidence=0.5,
                    reason=f"Unknown file type ({mime_type}), defaulting to OCR",
                    document_type=DocumentType.UNKNOWN
                )

        except Exception as e:
            logger.error(f"Error detecting document type: {e}")
            return ProcessingTrackRecommendation(
                track="ocr",
                confidence=0.3,
                reason=f"Error during detection: {str(e)}",
                document_type=DocumentType.UNKNOWN
            )

    def _analyze_pdf(self, file_path: Path) -> ProcessingTrackRecommendation:
        """
        Analyze PDF to determine if it's editable or scanned.

        Args:
            file_path: Path to PDF file

        Returns:
            Processing track recommendation
        """
        try:
            doc = fitz.open(str(file_path))
            total_pages = len(doc)

            # Sample pages for analysis
            pages_to_check = min(self.sample_pages, total_pages)
            text_pages = []
            page_details = []

            for page_num in range(pages_to_check):
                page = doc[page_num]

                # Extract text
                text = page.get_text()
                text_length = len(text.strip())

                # Check for images
                images = page.get_images()
                image_count = len(images)

                # Calculate page area covered by images
                page_rect = page.rect
                page_area = page_rect.width * page_rect.height
                image_area = 0

                for img in images:
                    try:
                        # Get image rectangles
                        xref = img[0]
                        img_rects = page.get_image_rects(xref)
                        for rect in img_rects:
                            image_area += rect.width * rect.height
                    except:
                        pass

                image_coverage = image_area / page_area if page_area > 0 else 0

                # Determine if page has meaningful text
                has_text = text_length >= self.min_text_length

                text_pages.append(has_text)
                page_details.append({
                    "page": page_num + 1,
                    "text_length": text_length,
                    "has_text": has_text,
                    "image_count": image_count,
                    "image_coverage": image_coverage
                })

                logger.debug(f"Page {page_num + 1}: text_length={text_length}, "
                           f"images={image_count}, image_coverage={image_coverage:.2%}")

            doc.close()

            # Calculate text coverage
            text_coverage = sum(text_pages) / len(text_pages) if text_pages else 0

            # Determine document type and track
            metadata = {
                "total_pages": total_pages,
                "sampled_pages": pages_to_check,
                "text_coverage": text_coverage,
                "page_details": page_details
            }

            if text_coverage >= self.text_coverage_threshold:
                # Mostly text-based PDF
                return ProcessingTrackRecommendation(
                    track="direct",
                    confidence=0.95,
                    reason=f"PDF has extractable text on {text_coverage:.0%} of sampled pages",
                    document_type=DocumentType.PDF_EDITABLE,
                    metadata=metadata
                )
            elif text_coverage <= 0.1:
                # Mostly scanned/image PDF
                return ProcessingTrackRecommendation(
                    track="ocr",
                    confidence=0.95,
                    reason=f"PDF appears to be scanned (only {text_coverage:.0%} pages have text)",
                    document_type=DocumentType.PDF_SCANNED,
                    metadata=metadata
                )
            else:
                # Mixed content
                # For mixed PDFs, we could implement page-level track selection in the future
                # For now, use OCR to ensure we don't miss scanned content
                return ProcessingTrackRecommendation(
                    track="ocr",
                    confidence=0.7,
                    reason=f"PDF has mixed content ({text_coverage:.0%} text pages), using OCR for completeness",
                    document_type=DocumentType.PDF_MIXED,
                    metadata=metadata
                )

        except Exception as e:
            logger.error(f"Error analyzing PDF: {e}")
            return ProcessingTrackRecommendation(
                track="ocr",
                confidence=0.5,
                reason=f"Error analyzing PDF: {str(e)}",
                document_type=DocumentType.PDF_SCANNED
            )

    def _analyze_image(self, file_path: Path, mime_type: str) -> ProcessingTrackRecommendation:
        """
        Analyze image file.

        Images always require OCR processing.
        """
        file_size = file_path.stat().st_size
        metadata = {
            "mime_type": mime_type,
            "file_size": file_size,
            "file_extension": file_path.suffix
        }

        return ProcessingTrackRecommendation(
            track="ocr",
            confidence=1.0,
            reason="Image files require OCR processing",
            document_type=DocumentType.IMAGE,
            metadata=metadata
        )

    def _analyze_office(self, file_path: Path, mime_type: str) -> ProcessingTrackRecommendation:
        """
        Analyze Office document by converting to PDF first.

        Strategy:
        1. Convert Office file to PDF using LibreOffice
        2. Analyze the converted PDF for text extractability
        3. Route to direct track if PDF has extractable text
        4. This significantly improves processing time (from >300s to ~2-5s)
        """
        document_type = self.OFFICE_MIMES.get(mime_type, DocumentType.UNKNOWN)
        file_size = file_path.stat().st_size

        base_metadata = {
            "mime_type": mime_type,
            "file_size": file_size,
            "file_extension": file_path.suffix,
            "original_document_type": document_type.value
        }

        try:
            # Initialize Office converter
            converter = OfficeConverter()

            # Create temporary directory for converted PDF
            with tempfile.TemporaryDirectory() as temp_dir:
                temp_path = Path(temp_dir)

                # Convert Office to PDF
                logger.info(f"Converting Office document to PDF: {file_path.name}")
                pdf_path = converter.convert_to_pdf(file_path, temp_path)
                logger.info(f"Office document converted to PDF: {pdf_path.name}")

                # Analyze the converted PDF for text extractability
                pdf_recommendation = self._analyze_pdf(pdf_path)

                # Merge metadata
                merged_metadata = {**base_metadata, **pdf_recommendation.metadata}
                merged_metadata["converted_pdf_analyzed"] = True

                # Determine final recommendation based on PDF analysis
                if pdf_recommendation.track == "direct":
                    # Converted PDF has extractable text - use direct track
                    return ProcessingTrackRecommendation(
                        track="direct",
                        confidence=pdf_recommendation.confidence * 0.95,  # Slightly lower confidence for converted files
                        reason=f"Office document converted to text-based PDF ({pdf_recommendation.metadata.get('text_coverage', 0):.0%} text coverage)",
                        document_type=document_type,  # Keep original Office type
                        metadata=merged_metadata
                    )
                else:
                    # Converted PDF is image-based or mixed - use OCR track
                    return ProcessingTrackRecommendation(
                        track="ocr",
                        confidence=pdf_recommendation.confidence,
                        reason=f"Office document converted to image-based PDF, requires OCR",
                        document_type=document_type,  # Keep original Office type
                        metadata=merged_metadata
                    )

        except OfficeConverterError as e:
            logger.error(f"Office conversion failed: {e}")
            # Fallback to OCR if conversion fails
            return ProcessingTrackRecommendation(
                track="ocr",
                confidence=0.7,
                reason=f"Office conversion failed ({str(e)}), using OCR as fallback",
                document_type=document_type,
                metadata=base_metadata
            )
        except Exception as e:
            logger.error(f"Error analyzing Office document: {e}")
            return ProcessingTrackRecommendation(
                track="ocr",
                confidence=0.5,
                reason=f"Error during Office analysis: {str(e)}",
                document_type=document_type,
                metadata=base_metadata
            )

    def _analyze_text(self, file_path: Path, mime_type: str) -> ProcessingTrackRecommendation:
        """
        Analyze text file.

        Plain text files can be directly processed without OCR.
        """
        file_size = file_path.stat().st_size
        metadata = {
            "mime_type": mime_type,
            "file_size": file_size,
            "file_extension": file_path.suffix
        }

        return ProcessingTrackRecommendation(
            track="direct",
            confidence=1.0,
            reason="Plain text files can be directly processed",
            document_type=DocumentType.TEXT,
            metadata=metadata
        )

    def analyze_batch(self, file_paths: List[Path]) -> Dict[str, ProcessingTrackRecommendation]:
        """
        Analyze multiple files and return recommendations.

        Args:
            file_paths: List of file paths to analyze

        Returns:
            Dictionary mapping file paths to recommendations
        """
        results = {}

        for file_path in file_paths:
            try:
                recommendation = self.detect(file_path)
                results[str(file_path)] = recommendation
                logger.info(f"Analyzed {file_path.name}: {recommendation.track} "
                          f"(confidence: {recommendation.confidence:.2f})")
            except Exception as e:
                logger.error(f"Error analyzing {file_path}: {e}")
                results[str(file_path)] = ProcessingTrackRecommendation(
                    track="ocr",
                    confidence=0.3,
                    reason=f"Error during analysis: {str(e)}",
                    document_type=DocumentType.UNKNOWN
                )

        return results

    def get_statistics(self, recommendations: Dict[str, ProcessingTrackRecommendation]) -> Dict:
        """
        Calculate statistics from batch analysis results.

        Args:
            recommendations: Dictionary of file recommendations

        Returns:
            Statistics dictionary
        """
        if not recommendations:
            return {"total": 0}

        tracks = [r.track for r in recommendations.values()]
        confidences = [r.confidence for r in recommendations.values()]
        doc_types = [r.document_type.value for r in recommendations.values()]

        stats = {
            "total": len(recommendations),
            "by_track": {
                "ocr": tracks.count("ocr"),
                "direct": tracks.count("direct")
            },
            "by_document_type": {},
            "confidence": {
                "mean": statistics.mean(confidences),
                "median": statistics.median(confidences),
                "min": min(confidences),
                "max": max(confidences)
            }
        }

        # Count by document type
        for doc_type in set(doc_types):
            stats["by_document_type"][doc_type] = doc_types.count(doc_type)

        return stats