# pylint: disable=arguments-renamed,broad-exception-caught

"""Output manager for saving processor results and VideoRAG outputs to disk."""

import json
import logging
import os
import re
from datetime import datetime
from typing import Optional
import numpy as np

from mini_videorag.core.processor import ProcessorResult
from mini_videorag.core.pipeline import PipelineResult
from mini_videorag.core.formatters import get_formatter, load_all_formatters

logger = logging.getLogger(__name__)


class NumpyEncoder(json.JSONEncoder):
    """Custom JSON encoder that handles numpy types."""

    def default(self, obj):
        if isinstance(obj, np.integer):
            return int(obj)
        if isinstance(obj, np.floating):
            return float(obj)
        if isinstance(obj, np.ndarray):
            return obj.tolist()
        if isinstance(obj, np.bool_):
            return bool(obj)
        return super().default(obj)


class OutputManager:
    """Manages outputs for video processing pipeline.

    Creates structured output directories and saves:
    - Processor results (JSON + human-readable formats)
    - Pipeline metadata
    - VideoRAG Q&A sessions
    - Markdown reports
    """

    def __init__(self, config: Optional[dict] = None):
        """Initialize output manager.

        Args:
            config: Configuration dict (from video_rag_config.yml)
                   If None, uses defaults (outputs disabled)
        """
        self.config = config or {}
        outputs_config = self.config.get("outputs", {})

        self.enabled = outputs_config.get("enabled", False)
        self.base_dir = outputs_config.get("base_dir", "outputs")
        self.save_images = outputs_config.get("save_images", True)
        self.generate_markdown = outputs_config.get("generate_markdown", True)

    def create_output_directory(self, video_id: str, timestamp: Optional[str] = None) -> str:
        """Create output directory for a video processing run.

        Args:
            video_id: Video identifier
            timestamp: Optional timestamp string (default: current time YYYYMMDD_HHMMSS)

        Returns:
            Path to created output directory
        """
        if not self.enabled:
            return ""

        # Sanitize video_id
        clean_id = re.sub(r"[^\w\-]", "_", video_id)

        # Generate timestamp if not provided
        if timestamp is None:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

        # Create directory structure
        output_dir = os.path.join(self.base_dir, f"{clean_id}_{timestamp}")
        os.makedirs(output_dir, exist_ok=True)
        os.makedirs(os.path.join(output_dir, "processors"), exist_ok=True)
        os.makedirs(os.path.join(output_dir, "video_rag"), exist_ok=True)
        os.makedirs(os.path.join(output_dir, "video_rag", "questions"), exist_ok=True)

        logger.info(f"Created output directory: {output_dir}")
        return output_dir

    def save_processor_result(self, processor_name: str, result: ProcessorResult, output_dir: str):
        """Save processor result to disk.

        Args:
            processor_name: Name of the processor
            result: ProcessorResult to save
            output_dir: Output directory path
        """
        if not self.enabled or not output_dir:
            return

        load_all_formatters()

        proc_dir = os.path.join(output_dir, "processors", processor_name)
        os.makedirs(proc_dir, exist_ok=True)

        result_json_path = os.path.join(proc_dir, "result.json")
        with open(result_json_path, "w", encoding="utf-8") as f:
            json.dump(
                self._serialize_result(result),
                f,
                indent=2,
                ensure_ascii=False,
                cls=NumpyEncoder,
            )

        try:
            formatter = get_formatter(processor_name)
            formatted = formatter(result)

            summary_path = os.path.join(proc_dir, "summary.txt")
            with open(summary_path, "w", encoding="utf-8") as f:
                f.write(formatted.summary_line + "\n\n")
                if formatted.detail_line:
                    f.write(f"Details: {formatted.detail_line}\n")

            if formatted.content_text:
                content_path = os.path.join(proc_dir, "content.txt")
                with open(content_path, "w", encoding="utf-8") as f:
                    f.write(formatted.content_text)

            logger.debug(f"Saved {processor_name} results to {proc_dir}")

        except Exception as e:
            logger.warning(f"Failed to format {processor_name} output: {e}")

    def save_pipeline_result(self, pipeline_result: PipelineResult, output_dir: str):
        """Save complete pipeline result.

        Args:
            pipeline_result: PipelineResult from pipeline.run_all()
            output_dir: Output directory path
        """
        if not self.enabled or not output_dir:
            return

        pipeline_json_path = os.path.join(output_dir, "pipeline_results.json")
        with open(pipeline_json_path, "w", encoding="utf-8") as f:
            json.dump(
                {
                    "video_id": pipeline_result.video_id,
                    "results": {
                        name: self._serialize_result(result)
                        for name, result in pipeline_result.results.items()
                    },
                },
                f,
                indent=2,
                ensure_ascii=False,
                cls=NumpyEncoder,
            )

        metadata_path = os.path.join(output_dir, "metadata.json")
        metadata = {
            "video_id": pipeline_result.video_id,
            "total_processors": len(pipeline_result.results),
            "successful": len(pipeline_result.successful()),
            "failed": len(pipeline_result.failed()),
            "processor_status": {
                name: result.success for name, result in pipeline_result.results.items()
            },
        }
        with open(metadata_path, "w", encoding="utf-8") as f:
            json.dump(metadata, f, indent=2, cls=NumpyEncoder)

        logger.info(f"Saved pipeline results to {output_dir}")

    def save_video_rag_question(
        self, question: str, answer: dict, output_dir: str, question_num: int
    ):
        """Save VideoRAG question/answer pair.

        Args:
            question: Question text
            answer: Answer object (as dict)
            output_dir: Output directory path
            question_num: Question number (for filename)
        """
        if not self.enabled or not output_dir:
            return

        questions_dir = os.path.join(output_dir, "video_rag", "questions")
        qa_path = os.path.join(questions_dir, f"question_{question_num}.json")

        with open(qa_path, "w", encoding="utf-8") as f:
            json.dump(
                {
                    "question": question,
                    "answer": answer,
                    "timestamp": datetime.now().isoformat(),
                },
                f,
                indent=2,
                ensure_ascii=False,
                cls=NumpyEncoder,
            )

        logger.debug(f"Saved Q&A {question_num} to {qa_path}")

    def generate_markdown_report(self, pipeline_result: PipelineResult, output_dir: str):
        """Generate markdown summary report.

        Args:
            pipeline_result: Pipeline results
            output_dir: Output directory path
        """
        if not self.enabled or not self.generate_markdown or not output_dir:
            return

        load_all_formatters()

        report_path = os.path.join(output_dir, "index.md")

        with open(report_path, "w", encoding="utf-8") as f:
            f.write("# Video Analysis Report\n\n")
            f.write(f"**Video ID:** `{pipeline_result.video_id}`\n\n")
            f.write(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")

            f.write("## Summary\n\n")
            f.write(f"- Total Processors: {len(pipeline_result.results)}\n")
            f.write(f"- Successful: {len(pipeline_result.successful())}\n")
            f.write(f"- Failed: {len(pipeline_result.failed())}\n\n")

            f.write("## Processor Results\n\n")

            for name, result in pipeline_result.results.items():
                status = "✅" if result.success else "❌"
                f.write(f"### {status} {name.replace('_', ' ').title()}\n\n")

                if result.success:
                    try:
                        formatted = get_formatter(name)(result)
                        f.write(f"**Summary:** {formatted.summary_line}\n\n")
                        if formatted.detail_line:
                            f.write(f"**Details:** {formatted.detail_line}\n\n")
                        if formatted.content_text and not formatted.is_empty:
                            f.write("<details>\n")
                            f.write("<summary>View Content</summary>\n\n")
                            f.write("```\n")
                            content = formatted.content_text[:1000]
                            if len(formatted.content_text) > 1000:
                                content += "\n... (truncated)"
                            f.write(content)
                            f.write("\n```\n</details>\n\n")
                    except Exception as e:
                        f.write(f"**Error formatting output:** {e}\n\n")
                else:
                    f.write(f"**Error:** {result.error}\n\n")

                f.write(f"[View full results](./processors/{name}/result.json)\n\n")
                f.write("---\n\n")

        logger.info(f"Generated markdown report: {report_path}")

    @staticmethod
    def _serialize_result(result: ProcessorResult) -> dict:
        """Serialize ProcessorResult to JSON-compatible dict.

        Args:
            result: ProcessorResult to serialize

        Returns:
            JSON-compatible dictionary
        """
        return {
            "processor_name": result.processor_name,
            "video_id": result.video_id,
            "success": result.success,
            "data": result.data,
            "segments": [
                {"start": seg.start, "end": seg.end, "metadata": seg.metadata}
                for seg in result.segments
            ],
            "metadata": result.metadata,
            "error": result.error,
        }
