"""
Core module for qry-doc library.

This module provides the QryDoc class, the main entry point (Facade)
for all library functionality.
"""
import os
from pathlib import Path
from typing import Any, Optional, Union

import pandas as pd

from qry_doc.ai_adapter import PandasAIAdapter
from qry_doc.chart_manager import ChartManager
from qry_doc.csv_exporter import CSVExporter
from qry_doc.data_source import DataSourceLoader
from qry_doc.exceptions import (
    QryDocError,
    QueryError,
    ExportError,
    ReportError,
    DataSourceError,
)
from qry_doc.report_generator import ReportGenerator
from qry_doc.report_template import ReportTemplate, DEFAULT_TEMPLATE
from qry_doc.validators import OutputValidator


class QryDoc:
    """
    Main entry point for qry-doc library.
    
    QryDoc acts as a Facade, providing a simple interface to:
    - Ask natural language questions about data
    - Extract query results to CSV
    - Generate PDF reports with charts and tables
    
    Example:
        ```python
        from qry_doc import QryDoc
        from pandasai.llm import OpenAI
        
        llm = OpenAI(api_token="your-api-key")
        qry = QryDoc("data.csv", llm=llm)
        
        # Ask a question
        answer = qry.ask("What is the average sales?")
        
        # Export to CSV
        qry.extract_to_csv("Show top 10 customers", "top_customers.csv")
        
        # Generate PDF report
        qry.generate_report("Analyze sales trends", "report.pdf")
        ```
    """
    
    # Environment variable for API key fallback
    API_KEY_ENV_VAR = "OPENAI_API_KEY"
    
    def __init__(
        self,
        data_source: Union[str, Path, pd.DataFrame],
        llm: Any,
        api_key: Optional[str] = None
    ) -> None:
        """
        Initialize QryDoc with a data source and LLM provider.
        
        Args:
            data_source: Path to CSV file, pandas DataFrame, or SQL connection string.
            llm: LLM provider instance (OpenAI, Anthropic, LiteLLM, etc.).
            api_key: Optional API key (falls back to environment variable).
            
        Raises:
            DataSourceError: If the data source cannot be loaded.
        """
        # Load data
        self._df = DataSourceLoader.load(data_source)
        
        # Store LLM
        self._llm = llm
        
        # Handle API key (for reference, actual key is in llm)
        self._api_key = api_key or os.environ.get(self.API_KEY_ENV_VAR)
        
        # Initialize chart manager
        self._chart_manager = ChartManager()
        
        # Initialize AI adapter
        self._adapter = PandasAIAdapter(
            df=self._df,
            llm=self._llm,
            chart_save_path=self._chart_manager.path
        )
    
    def ask(self, query: str) -> str:
        """
        Ask a natural language question about the data.
        
        Args:
            query: Natural language question.
            
        Returns:
            A human-readable text response.
            
        Raises:
            QueryError: If the query cannot be processed.
            
        Example:
            ```python
            answer = qry.ask("What is the total revenue for 2023?")
            print(answer)  # "The total revenue for 2023 is $1,234,567"
            ```
        """
        try:
            return self._adapter.query_as_text(query)
        except QueryError:
            raise
        except Exception as e:
            sanitized = OutputValidator.sanitize_error_message(e)
            raise QueryError(
                user_message=f"No se pudo interpretar la consulta: {sanitized}. "
                            "Intente reformular la pregunta con más detalle.",
                internal_error=e
            )
    
    def extract_to_csv(
        self,
        query: str,
        output_path: Union[str, Path],
        include_index: bool = False
    ) -> str:
        """
        Execute a query and export results to CSV.
        
        Args:
            query: Natural language query requesting tabular data.
            output_path: Path for the output CSV file.
            include_index: Whether to include DataFrame index. Default False.
            
        Returns:
            Confirmation message with the output file path.
            
        Raises:
            QueryError: If the query cannot be processed.
            ExportError: If the result is not tabular or export fails.
            
        Example:
            ```python
            result = qry.extract_to_csv(
                "Show sales by region for Q4",
                "q4_sales.csv"
            )
            print(result)  # "Datos exportados exitosamente a q4_sales.csv"
            ```
        """
        try:
            # Get DataFrame result
            df = self._adapter.query_as_dataframe(query)
            
            # Validate result
            is_valid, error_msg = OutputValidator.validate_dataframe(df)
            if not is_valid:
                raise ExportError(user_message=error_msg)
            
            # Export to CSV
            return CSVExporter.export(
                df=df,
                path=output_path,
                include_index=include_index
            )
            
        except (QueryError, ExportError):
            raise
        except Exception as e:
            sanitized = OutputValidator.sanitize_error_message(e)
            raise ExportError(
                user_message=f"Error al exportar datos: {sanitized}",
                internal_error=e
            )
    
    def export_dataframe(
        self,
        output_path: Union[str, Path],
        include_index: bool = False
    ) -> str:
        """
        Export the entire underlying DataFrame to CSV.
        
        This is useful when you want to export all data without
        running a query through the LLM.
        
        Args:
            output_path: Path for the output CSV file.
            include_index: Whether to include DataFrame index. Default False.
            
        Returns:
            Confirmation message with the output file path.
            
        Example:
            ```python
            result = qry.export_dataframe("all_data.csv")
            ```
        """
        return CSVExporter.export(
            df=self._df,
            path=output_path,
            include_index=include_index
        )
    
    def filter_and_export(
        self,
        output_path: Union[str, Path],
        columns: Optional[list[str]] = None,
        filters: Optional[dict[str, Any]] = None,
        include_index: bool = False
    ) -> str:
        """
        Filter the DataFrame and export to CSV without using LLM.
        
        This provides a reliable way to export data when LLM queries
        are not returning consistent results.
        
        Args:
            output_path: Path for the output CSV file.
            columns: List of columns to include. None = all columns.
            filters: Dict of column:value pairs to filter by.
            include_index: Whether to include DataFrame index.
            
        Returns:
            Confirmation message with the output file path.
            
        Example:
            ```python
            # Export specific columns
            qry.filter_and_export(
                "ventas_norte.csv",
                columns=["producto", "cantidad", "precio"],
                filters={"region": "Norte"}
            )
            ```
        """
        df = self._df.copy()
        
        # Apply filters
        if filters:
            for col, value in filters.items():
                if col in df.columns:
                    df = df[df[col] == value]
        
        # Select columns
        if columns:
            valid_cols = [c for c in columns if c in df.columns]
            if valid_cols:
                df = df[valid_cols]
        
        return CSVExporter.export(
            df=df,
            path=output_path,
            include_index=include_index
        )
    
    def generate_report(
        self,
        query: str,
        output_path: Union[str, Path],
        title: str = "Reporte Automático",
        template: Optional[ReportTemplate] = None,
        include_chart: bool = True,
        include_table: bool = True,
        chart_query: Optional[str] = None
    ) -> str:
        """
        Generate a PDF report based on a query.
        
        The report includes:
        - Title and executive summary
        - Relevant charts/visualizations (if applicable)
        - Data tables (if applicable)
        
        Args:
            query: Natural language query describing the analysis.
            output_path: Path for the output PDF file.
            title: Report title. Default "Reporte Automático".
            template: Optional ReportTemplate for custom styling.
            include_chart: Whether to include a chart. Default True.
            include_table: Whether to include a data table. Default True.
            chart_query: Optional specific query for chart generation.
                        If not provided, uses the main query.
            
        Returns:
            Confirmation message with the output file path.
            
        Raises:
            QueryError: If the query cannot be processed.
            ReportError: If report generation fails.
            
        Example:
            ```python
            from qry_doc import ReportTemplate
            
            template = ReportTemplate(
                logo_path="logo.png",
                primary_color="#003366"
            )
            
            result = qry.generate_report(
                "Analyze customer demographics",
                "demographics_report.pdf",
                title="Customer Demographics Analysis",
                template=template,
                include_chart=True,
                chart_query="Show a bar chart of customers by age group"
            )
            ```
        """
        try:
            # Clear previous charts
            self._chart_manager.clear_charts()
            
            # Step 1: Get textual summary
            summary = self._adapter.query_as_text(
                f"Analyze the data regarding: {query}. Provide a comprehensive summary."
            )
            
            # Step 2: Try to generate a chart
            chart_path = None
            if include_chart:
                try:
                    chart_prompt = chart_query or query
                    self._adapter.query(
                        f"Create a chart or visualization showing: {chart_prompt}. "
                        "Use matplotlib to create a clear, professional chart."
                    )
                    chart_path = self._chart_manager.get_latest_chart()
                except Exception:
                    # Chart generation is optional
                    pass
            
            # Step 3: Try to get tabular data
            table_data = None
            if include_table:
                try:
                    table_data = self._adapter.query_as_dataframe(
                        f"Create a summary table for: {query}"
                    )
                    # Validate table
                    is_valid, _ = OutputValidator.validate_dataframe(table_data)
                    if not is_valid:
                        table_data = None
                except Exception:
                    # Table is optional
                    pass
            
            # Step 4: Generate PDF
            generator = ReportGenerator(
                output_path=output_path,
                template=template or DEFAULT_TEMPLATE
            )
            
            generator.build(
                title=title,
                summary=summary,
                chart_path=chart_path,
                dataframe=table_data
            )
            
            return f"Reporte generado exitosamente en {output_path}"
            
        except (QueryError, ReportError):
            raise
        except Exception as e:
            sanitized = OutputValidator.sanitize_error_message(e)
            raise ReportError(
                user_message=f"Error al generar el reporte: {sanitized}",
                internal_error=e
            )
        finally:
            # Clean up charts
            self._chart_manager.clear_charts()
    
    def generate_chart(
        self,
        query: str,
        output_path: Optional[Union[str, Path]] = None
    ) -> Optional[Path]:
        """
        Generate a chart based on a query.
        
        Args:
            query: Natural language query describing the visualization.
            output_path: Optional path to save the chart. If not provided,
                        returns the path to the temporary chart file.
            
        Returns:
            Path to the generated chart file, or None if generation failed.
            
        Example:
            ```python
            chart_path = qry.generate_chart(
                "Show a bar chart of sales by category",
                "sales_chart.png"
            )
            ```
        """
        try:
            self._chart_manager.clear_charts()
            
            self._adapter.query(
                f"Create a chart or visualization showing: {query}. "
                "Use matplotlib to create a clear, professional chart with labels."
            )
            
            chart_path = self._chart_manager.get_latest_chart()
            
            if chart_path and output_path:
                import shutil
                output_path = Path(output_path)
                output_path.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy(chart_path, output_path)
                return output_path
            
            return chart_path
            
        except Exception:
            return None
    
    @property
    def dataframe(self) -> pd.DataFrame:
        """Get the underlying DataFrame."""
        return self._df
    
    @property
    def columns(self) -> list[str]:
        """Get the column names of the data."""
        return list(self._df.columns)
    
    @property
    def shape(self) -> tuple[int, int]:
        """Get the shape of the data (rows, columns)."""
        return self._df.shape
    
    def __enter__(self) -> "QryDoc":
        """Enter context manager."""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Exit context manager, cleaning up resources."""
        self._chart_manager.cleanup()
    
    def __del__(self) -> None:
        """Destructor - ensure cleanup."""
        if hasattr(self, '_chart_manager'):
            self._chart_manager.cleanup()
