"""
Content Extractor utility for extracting text from various file types.
Extracted from document_base.py ContentExtractor class.
"""
import io
import os
import re
import tempfile
from typing import List, Optional
from PIL import Image
import pandas as pd

from app.services.document_nodes import timer


class ContentExtractor:
    """
    Static utility class for extracting text content from various file types.
    Supports PDF, Excel, CSV, DOCX, DOC (Word 97-2003), TXT, and images (via OCR).
    """
    
    @staticmethod
    def _mostly_whitespace(s: str) -> bool:
        """Check if string is mostly whitespace"""
        if not s:
            return True
        non_ws = len(re.sub(r"\s+", "", s))
        return non_ws < max(40, int(0.10 * len(s)))
    
    @staticmethod
    def _page_to_image(page, dpi: int = 300) -> Image.Image:
        """Render a page to a PIL Image"""
        try:
            import pymupdf
            mat = pymupdf.Matrix(dpi / 72, dpi / 72)
            pix = page.get_pixmap(matrix=mat, alpha=False)
            img_bytes = pix.tobytes("png")
            return Image.open(io.BytesIO(img_bytes))
        except ImportError:
            raise ImportError("pymupdf is required for PDF processing")
    
    @staticmethod
    def _configure_tesseract():
        """Configure Tesseract path for OCR"""
        try:
            import pytesseract
            import os
            import platform
            from app.core.config import settings
            
            # Check settings first
            if hasattr(settings, 'TESSERACT_PATH') and settings.TESSERACT_PATH and os.path.exists(settings.TESSERACT_PATH):
                pytesseract.pytesseract.tesseract_cmd = settings.TESSERACT_PATH
                return True
            
            # Check for TESSERACT_PATH in environment variable directly
            env_path = os.getenv('TESSERACT_PATH')
            if env_path and os.path.exists(env_path):
                pytesseract.pytesseract.tesseract_cmd = env_path
                return True

            # If still not found, try to get from AdminConfiguration table (only if needed)
            try:
                from app.services.agent_config import get_all_admin_configs
                org_configs = get_all_admin_configs(config_type="C")
                org_data = {item['configuration_name']: item['value'] for item in org_configs}
                db_path = org_data.get("TESSERACT_PATH", "")
                if db_path and os.path.exists(db_path):
                    pytesseract.pytesseract.tesseract_cmd = db_path
                    return True
            except Exception:
                pass

            
            # Fallback for Windows
            if platform.system() == 'Windows':
                tesseract_paths = [
                    r'C:\Program Files\Tesseract-OCR\tesseract.exe',
                    r'C:\Program Files (x86)\Tesseract-OCR\tesseract.exe',
                    r'C:\Tesseract-OCR\tesseract.exe',
                    os.path.expanduser(r'~\AppData\Local\Programs\Tesseract-OCR\tesseract.exe'),
                ]
                for path in tesseract_paths:
                    if os.path.exists(path):
                        pytesseract.pytesseract.tesseract_cmd = path
                        return True
            return False
        except Exception:
            return False

    @staticmethod
    def _ocr_image(img: Image.Image) -> str:
        """Perform OCR on image using Tesseract"""
        try:
            import pytesseract
            with timer("ocr.tesseract"):
                ContentExtractor._configure_tesseract()
                return pytesseract.image_to_string(img, lang="eng")
        except ImportError:
            return "Error: pytesseract not installed"
    
    @staticmethod
    def extract_from_pdf(
        file,
        force_ocr: bool = False,
        ocr_dpi: int = 300,
        should_cancel=None,
    ) -> str:
        """
        Extract text from PDF using PyMuPDF's native text first; 
        fall back to Tesseract OCR when needed.
        """
        try:
            import pymupdf
            print(f"[FILE] extract_from_pdf start: force_ocr={force_ocr}, dpi={ocr_dpi}")
            
            # Handle file input (path string or file object)
            if isinstance(file, str):
                doc = pymupdf.open(file)
            else:
                if hasattr(file, 'read'):
                    data = file.read()
                    if hasattr(file, 'seek'):
                        file.seek(0)
                else:
                    data = file
                
                if not data:
                    return "Error: Empty file"
                
                doc = pymupdf.open(stream=data, filetype="pdf")
            
            # Handle password-protected PDFs
            if doc.needs_pass:
                common_passwords = ["", "password", "123456"]
                authenticated = False
                for pwd in common_passwords:
                    if doc.authenticate(pwd):
                        authenticated = True
                        break
                
                if not authenticated:
                    return "Error: PDF is password protected and requires authentication"
            
            all_text = []
            print(f"[FILE] extract_from_pdf pages={doc.page_count}")
            
            with timer("extract.pdf.total"):
                for i in range(doc.page_count):
                    if should_cancel and should_cancel():
                        print(
                            f"[FILE] extract_from_pdf cancelled after page {i} of {doc.page_count}",
                            flush=True,
                        )
                        break
                    with timer(f"extract.pdf.page(i={i})"):
                        page = doc.load_page(i)
                        page_text = page.get_text("text") if not force_ocr else ""
                
                        if force_ocr or not page_text or ContentExtractor._mostly_whitespace(page_text):
                            try:
                                with timer(f"extract.pdf.page_ocr(i={i})"):
                                    img = ContentExtractor._page_to_image(page, dpi=ocr_dpi)
                                    page_text = ContentExtractor._ocr_image(img)
                            except Exception as ocr_error:
                                page_text = page_text or ""
                                print(f"OCR failed for page {i + 1}: {str(ocr_error)}")
                
                        all_text.append(page_text)
            
            doc.close()
            
            full_text = "\n\n".join(all_text)
            print(f"[FILE] extract_from_pdf done: chars={len(full_text)}")
            return full_text.strip() if full_text.strip() else "No text extracted from PDF"
            
        except ImportError:
            return "Error: pymupdf is required for PDF processing"
        except Exception as e:
            return f"Error reading PDF: {str(e)}"

    @staticmethod
    def extract_pdf_page_texts(
        file,
        force_ocr: bool = False,
        ocr_dpi: int = 300,
        should_cancel=None,
    ) -> List[str]:
        """
        Extract per-page text from a PDF (same logic as extract_from_pdf).
        Returns one string per page for page-span citation at index time.
        """
        try:
            import pymupdf

            if isinstance(file, str):
                doc = pymupdf.open(file)
            else:
                if hasattr(file, "read"):
                    data = file.read()
                    if hasattr(file, "seek"):
                        file.seek(0)
                else:
                    data = file
                if not data:
                    return []
                doc = pymupdf.open(stream=data, filetype="pdf")

            if doc.needs_pass:
                authenticated = False
                for pwd in ("", "password", "123456"):
                    if doc.authenticate(pwd):
                        authenticated = True
                        break
                if not authenticated:
                    doc.close()
                    return []

            page_texts: List[str] = []
            for i in range(doc.page_count):
                if should_cancel and should_cancel():
                    break
                page = doc.load_page(i)
                page_text = page.get_text("text") if not force_ocr else ""
                if force_ocr or not page_text or ContentExtractor._mostly_whitespace(page_text):
                    try:
                        img = ContentExtractor._page_to_image(page, dpi=ocr_dpi)
                        page_text = ContentExtractor._ocr_image(img)
                    except Exception:
                        page_text = page_text or ""
                page_texts.append(page_text or "")
            doc.close()
            return page_texts
        except Exception:
            return []

    @staticmethod
    def extract_office_page_texts(file_path: str) -> List[str]:
        """
        Per-page text for DOC/DOCX via temporary PDF conversion (pandoc + PyMuPDF).
        Returns [] on conversion failure so callers can fall back to legacy chunking.
        """
        if not file_path or not os.path.isfile(file_path):
            return []
        cleanup_paths: List[str] = []
        try:
            from app.utils.document_preview import convert_office_file_to_pdf

            pdf_path, cleanup_paths = convert_office_file_to_pdf(file_path)
            return ContentExtractor.extract_pdf_page_texts(pdf_path)
        except Exception:
            return []
        finally:
            for path in cleanup_paths:
                try:
                    if path and os.path.isfile(path):
                        os.remove(path)
                    elif path and os.path.isdir(path):
                        import shutil

                        shutil.rmtree(path, ignore_errors=True)
                except OSError:
                    pass
    
    @staticmethod
    def extract_from_excel(file) -> str:
        """Extract text from Excel file"""
        try:
            df = pd.read_excel(file, sheet_name=None)
            content = []
            for sheet_name, sheet_data in df.items():
                content.append(f"Sheet: {sheet_name}\n")
                content.append(sheet_data.to_string())
            return "\n\n".join(content)
        except Exception as e:
            return f"Error reading Excel: {str(e)}"
    
    @staticmethod
    def extract_from_csv(file) -> str:
        """Extract text from CSV file"""
        try:
            df = pd.read_csv(file)
            return df.to_string()
        except Exception as e:
            return f"Error reading CSV: {str(e)}"
    
    @staticmethod
    def extract_from_docx(file) -> str:
        """Extract text from DOCX file with enhanced error handling"""
        try:
            import docx
            doc = docx.Document(file)
            text = []
            
            for paragraph in doc.paragraphs:
                if paragraph.text.strip():
                    text.append(paragraph.text)
            
            for table in doc.tables:
                for row in table.rows:
                    row_text = []
                    for cell in row.cells:
                        if cell.text.strip():
                            row_text.append(cell.text.strip())
                    if row_text:
                        text.append(" | ".join(row_text))
            
            result = "\n".join(text)
            return result if result.strip() else "No text extracted from DOCX"
            
        except ImportError:
            return "Error: python-docx is required for DOCX processing"
        except Exception as e:
            return f"Error reading DOCX: {str(e)}"
    
    @staticmethod
    def extract_from_doc(file) -> str:
        """
        Extract text from legacy .doc (Word 97-2003) file using pypandoc.
        Requires pandoc binary to be installed (e.g. apt-get install pandoc, or choco install pandoc).
        """
        try:
            import pypandoc  # type: ignore[import-untyped]
            path = None
            if isinstance(file, str) and os.path.isfile(file):
                path = file
            else:
                data = file.read() if hasattr(file, "read") else file
                if hasattr(file, "seek"):
                    file.seek(0)
                if not data:
                    return "Error: Empty file"
                fd, path = tempfile.mkstemp(suffix=".doc")
                try:
                    os.write(fd, data)
                    os.close(fd)
                except Exception:
                    os.close(fd)
                    raise
            try:
                text = pypandoc.convert_file(path, "plain", extra_args=["--wrap=none"])
                result = (text or "").strip()
                return result if result else "No text extracted from DOC"
            finally:
                if path and not (isinstance(file, str) and file == path):
                    try:
                        os.unlink(path)
                    except OSError:
                        pass
        except ImportError:
            return "Error: pypandoc is required for .doc processing. Install with: pip install pypandoc_binary (includes pandoc), or pip install pypandoc and install the pandoc binary from https://pandoc.org/installing.html"
        except Exception as e:
            err_msg = str(e)
            if "pandoc" in err_msg.lower() and ("not found" in err_msg.lower() or "was found" in err_msg.lower()):
                return "Error reading DOC: The pandoc binary is required. Install with: pip install pypandoc_binary (includes pandoc), or install pandoc from https://pandoc.org/installing.html"
            return f"Error reading DOC: {err_msg}"
    
    @staticmethod
    def extract_from_txt(file) -> str:
        """Extract text from TXT file"""
        try:
            if isinstance(file, str):
                with open(file, 'r', encoding='utf-8') as f:
                    return f.read()
            else:
                if hasattr(file, 'read'):
                    content = file.read()
                    if isinstance(content, bytes):
                        return content.decode('utf-8')
                    return content
                return file.decode('utf-8')
        except UnicodeDecodeError:
            try:
                if isinstance(file, str):
                    with open(file, 'r', encoding='latin-1') as f:
                        return f.read()
                else:
                    if hasattr(file, 'seek'):
                        file.seek(0)
                    content = file.read() if hasattr(file, 'read') else file
                    return content.decode('latin-1')
            except Exception as e:
                return f"Error reading TXT with alternative encoding: {str(e)}"
        except Exception as e:
            return f"Error reading TXT: {str(e)}"
    
    @staticmethod
    def extract_from_image(file, dpi: int = 300) -> str:
        """Extract text from image using OCR with enhanced preprocessing"""
        try:
            import pytesseract
            print(f"[FILE] extract_from_image start: dpi={dpi}")
            ContentExtractor._configure_tesseract()
            
            if isinstance(file, str):
                image = Image.open(file)
            else:
                if hasattr(file, 'seek'):
                    file.seek(0)
                image = Image.open(file)
            
            if image.mode not in ('RGB', 'L'):
                image = image.convert('RGB')
            
            with timer("extract.image.tesseract"):
                text = pytesseract.image_to_string(image, lang="eng")
            
            result = text.strip()
            print(f"[FILE] extract_from_image done: chars={len(result)}")
            return result if result else "No text detected in image"
            
        except ImportError:
            return "Error: pytesseract is required for image OCR"
        except Exception as e:
            try:
                if isinstance(file, str):
                    image = Image.open(file)
                else:
                    if hasattr(file, 'seek'):
                        file.seek(0)
                    image = Image.open(file)
                
                pdf_bytes = io.BytesIO()
                image_rgb = image.convert('RGB')
                image_rgb.save(pdf_bytes, format='PDF')
                pdf_bytes.seek(0)
                
                return ContentExtractor.extract_from_pdf(pdf_bytes, force_ocr=True, ocr_dpi=dpi)
                
            except Exception as fallback_error:
                return f"Error reading image: {str(e)} | Fallback error: {str(fallback_error)}"
