"""
PDF report generation for qry-doc.

This module provides the ReportGenerator class for creating
professional PDF reports using ReportLab/Platypus.
"""
from pathlib import Path
from typing import Any, Optional, Union

import pandas as pd
from reportlab.platypus import (
    SimpleDocTemplate,
    Paragraph,
    Spacer,
    Image,
    Table,
    TableStyle,
    PageBreak,
    BaseDocTemplate,
    Frame,
    PageTemplate,
)
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.colors import HexColor, black, white
from reportlab.lib.units import inch
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY

from qry_doc.report_template import ReportTemplate, DEFAULT_TEMPLATE
from qry_doc.exceptions import ReportError
from qry_doc.validators import OutputValidator


class ReportGenerator:
    """
    Generates PDF reports using ReportLab/Platypus.
    
    Features:
    - Automatic table column width adjustment
    - Chart/image embedding
    - Custom template support
    - Professional styling
    """
    
    # Minimum column width in points
    MIN_COL_WIDTH = 30
    # Maximum characters before text wrapping
    MAX_CELL_CHARS = 50
    # Font size reduction steps for wide tables
    FONT_SIZE_STEPS = [10, 9, 8, 7, 6]
    
    def __init__(
        self,
        output_path: Union[str, Path],
        template: Optional[ReportTemplate] = None
    ) -> None:
        """
        Initialize the report generator.
        
        Args:
            output_path: Path for the output PDF file.
            template: Optional ReportTemplate for styling.
        """
        self.output_path = Path(output_path)
        self.template = template or DEFAULT_TEMPLATE
        self.story: list[Any] = []
        self.styles = self._create_styles()
        self._header_footer_call_count = 0
    
    def _create_styles(self) -> dict[str, ParagraphStyle]:
        """Create custom paragraph styles based on template."""
        base_styles = getSampleStyleSheet()
        
        # Custom title style
        title_style = ParagraphStyle(
            'CustomTitle',
            parent=base_styles['Title'],
            fontName=self.template.title_font,
            fontSize=24,
            textColor=self.template.primary_color_obj,
            spaceAfter=20,
            alignment=TA_CENTER,
        )
        
        # Custom heading style
        heading_style = ParagraphStyle(
            'CustomHeading',
            parent=base_styles['Heading2'],
            fontName=self.template.title_font,
            fontSize=14,
            textColor=self.template.primary_color_obj,
            spaceBefore=15,
            spaceAfter=10,
        )
        
        # Custom body style
        body_style = ParagraphStyle(
            'CustomBody',
            parent=base_styles['Normal'],
            fontName=self.template.body_font,
            fontSize=10,
            leading=14,
            alignment=TA_JUSTIFY,
            spaceAfter=10,
        )
        
        return {
            'Title': title_style,
            'Heading': heading_style,
            'Body': body_style,
            'Normal': base_styles['Normal'],
        }
    
    def build(
        self,
        title: str,
        summary: str,
        chart_path: Optional[Path] = None,
        dataframe: Optional[pd.DataFrame] = None
    ) -> None:
        """
        Build and save the PDF report.
        
        Args:
            title: Report title.
            summary: Executive summary text.
            chart_path: Optional path to chart image.
            dataframe: Optional DataFrame to render as table.
            
        Raises:
            ReportError: If report generation fails.
        """
        try:
            # Reset story
            self.story = []
            self._header_footer_call_count = 0
            
            # Add title
            self.story.append(Paragraph(title, self.styles['Title']))
            self.story.append(Spacer(1, 20))
            
            # Add summary section
            self.story.append(Paragraph("Análisis Ejecutivo", self.styles['Heading']))
            
            # Split summary into paragraphs
            for para in summary.split('\n\n'):
                if para.strip():
                    self.story.append(Paragraph(para.strip(), self.styles['Body']))
            
            self.story.append(Spacer(1, 15))
            
            # Add chart if provided
            if chart_path:
                self._add_chart(chart_path)
            
            # Add table if provided
            if dataframe is not None:
                self._add_table(dataframe)
            
            # Build the document
            self._build_document()
            
        except Exception as e:
            sanitized = OutputValidator.sanitize_error_message(e)
            raise ReportError(
                user_message=f"Error al generar el reporte: {sanitized}",
                internal_error=e
            )
    
    def _add_chart(self, chart_path: Path) -> None:
        """Add a chart image to the report."""
        # Verify file exists
        is_valid, error_msg = OutputValidator.verify_file_exists(chart_path)
        if not is_valid:
            # Skip chart if file doesn't exist
            return
        
        try:
            # Calculate image dimensions to fit content width
            max_width = self.template.content_width
            max_height = 300  # Maximum height in points
            
            img = Image(str(chart_path))
            
            # Scale to fit
            img_width, img_height = img.imageWidth, img.imageHeight
            scale = min(max_width / img_width, max_height / img_height, 1.0)
            
            img.drawWidth = img_width * scale
            img.drawHeight = img_height * scale
            
            self.story.append(Paragraph("Visualización", self.styles['Heading']))
            self.story.append(img)
            self.story.append(Spacer(1, 15))
            
        except Exception:
            # Skip chart if it can't be loaded
            pass
    
    def _add_table(self, df: pd.DataFrame) -> None:
        """Add a DataFrame as a table to the report."""
        # Validate DataFrame
        is_valid, error_msg = OutputValidator.validate_dataframe(df)
        if not is_valid:
            return
        
        self.story.append(Paragraph("Datos", self.styles['Heading']))
        
        # Create table with auto-adjusted widths
        table = self._auto_adjust_table(df)
        self.story.append(table)
        self.story.append(Spacer(1, 15))
    
    def _auto_adjust_table(self, df: pd.DataFrame) -> Table:
        """
        Create a table with automatically adjusted column widths.
        
        Args:
            df: DataFrame to convert to table.
            
        Returns:
            ReportLab Table object.
        """
        # Convert DataFrame to list of lists
        data = [df.columns.tolist()] + df.values.tolist()
        
        # Convert all values to strings and wrap long text
        wrapped_data = []
        for row in data:
            wrapped_row = []
            for cell in row:
                cell_str = str(cell) if cell is not None else ""
                # Wrap long text in Paragraph for text wrapping
                if len(cell_str) > self.MAX_CELL_CHARS:
                    wrapped_row.append(Paragraph(cell_str, self.styles['Normal']))
                else:
                    wrapped_row.append(cell_str)
            wrapped_data.append(wrapped_row)
        
        # Calculate column widths
        col_widths = self._calculate_column_widths(df, self.template.content_width)
        
        # Determine font size based on table width
        font_size = self._determine_font_size(col_widths, self.template.content_width)
        
        # Create table
        table = Table(wrapped_data, colWidths=col_widths, repeatRows=1)
        
        # Apply styling
        style = TableStyle([
            # Header styling
            ('BACKGROUND', (0, 0), (-1, 0), self.template.primary_color_obj),
            ('TEXTCOLOR', (0, 0), (-1, 0), white),
            ('FONTNAME', (0, 0), (-1, 0), self.template.title_font),
            ('FONTSIZE', (0, 0), (-1, 0), font_size),
            ('BOTTOMPADDING', (0, 0), (-1, 0), 8),
            ('TOPPADDING', (0, 0), (-1, 0), 8),
            
            # Body styling
            ('FONTNAME', (0, 1), (-1, -1), self.template.body_font),
            ('FONTSIZE', (0, 1), (-1, -1), font_size - 1),
            ('BOTTOMPADDING', (0, 1), (-1, -1), 5),
            ('TOPPADDING', (0, 1), (-1, -1), 5),
            
            # Grid
            ('GRID', (0, 0), (-1, -1), 0.5, HexColor("#cccccc")),
            
            # Alternating row colors
            ('ROWBACKGROUNDS', (0, 1), (-1, -1), [white, HexColor("#f5f5f5")]),
            
            # Alignment
            ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
            ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
        ])
        
        table.setStyle(style)
        return table
    
    def _calculate_column_widths(
        self,
        df: pd.DataFrame,
        available_width: float
    ) -> list[float]:
        """
        Calculate optimal column widths based on content.
        
        Args:
            df: DataFrame to analyze.
            available_width: Available width in points.
            
        Returns:
            List of column widths in points.
        """
        n_cols = len(df.columns)
        
        # Calculate content-based widths
        widths = []
        for col in df.columns:
            # Get max length in column (including header)
            max_len = max(
                len(str(col)),
                df[col].astype(str).str.len().max() if len(df) > 0 else 0
            )
            # Estimate width (roughly 7 points per character)
            estimated_width = max(max_len * 7, self.MIN_COL_WIDTH)
            widths.append(estimated_width)
        
        # Scale to fit available width
        total_width = sum(widths)
        if total_width > available_width:
            scale = available_width / total_width
            widths = [max(w * scale, self.MIN_COL_WIDTH) for w in widths]
        
        # Distribute remaining space
        total_width = sum(widths)
        if total_width < available_width:
            extra = (available_width - total_width) / n_cols
            widths = [w + extra for w in widths]
        
        return widths
    
    def _determine_font_size(
        self,
        col_widths: list[float],
        available_width: float
    ) -> int:
        """
        Determine appropriate font size based on table width.
        
        Args:
            col_widths: List of column widths.
            available_width: Available width in points.
            
        Returns:
            Font size in points.
        """
        total_width = sum(col_widths)
        
        # If table fits comfortably, use default size
        if total_width <= available_width * 0.9:
            return self.FONT_SIZE_STEPS[0]
        
        # Reduce font size for wider tables
        ratio = total_width / available_width
        if ratio > 1.5:
            return self.FONT_SIZE_STEPS[-1]
        elif ratio > 1.3:
            return self.FONT_SIZE_STEPS[-2]
        elif ratio > 1.1:
            return self.FONT_SIZE_STEPS[-3]
        else:
            return self.FONT_SIZE_STEPS[1]
    
    def _build_document(self) -> None:
        """Build the final PDF document with template."""
        # Ensure parent directory exists
        self.output_path.parent.mkdir(parents=True, exist_ok=True)
        
        # Create document with custom page template
        doc = BaseDocTemplate(
            str(self.output_path),
            pagesize=self.template.page_size,
            leftMargin=self.template.margin_left,
            rightMargin=self.template.margin_right,
            topMargin=self.template.margin_top + self.template.header_height,
            bottomMargin=self.template.margin_bottom + self.template.footer_height,
        )
        
        # Create frame for content
        frame = Frame(
            self.template.margin_left,
            self.template.margin_bottom + self.template.footer_height,
            self.template.content_width,
            self.template.content_height,
            id='normal'
        )
        
        # Create page template with header/footer
        def on_page(canvas, doc):
            self._header_footer_call_count += 1
            self.template.draw_header(canvas, doc)
            self.template.draw_footer(canvas, doc)
        
        page_template = PageTemplate(
            id='main',
            frames=[frame],
            onPage=on_page
        )
        
        doc.addPageTemplates([page_template])
        
        # Build document
        doc.build(self.story)
    
    @property
    def header_footer_calls(self) -> int:
        """Get the number of times header/footer were drawn (equals page count)."""
        return self._header_footer_call_count
