"""
ErrorReportGenerator - Create user-friendly error reports
"""

import re
from typing import Any, Dict, Optional

from loguru import logger

from .error_reporter import ErrorReporter


class ErrorReportGenerator:
    """
    Generates comprehensive, user-friendly error reports
    """

    def __init__(self, llm=None):
        """
        Initialize error report generator

        Args:
            llm: Optional LLM instance (unused, kept for compatibility)
        """
        self.error_reporter = ErrorReporter()

    def generate_error_report(
        self,
        error_message: str,
        query: str,
        partial_results: Optional[Dict[str, Any]] = None,
        search_iterations: int = 0,
        research_id: Optional[str] = None,
    ) -> str:
        """
        Generate a comprehensive error report

        Args:
            error_message: The error that occurred
            query: The research query
            partial_results: Any partial results that were collected
            search_iterations: Number of search iterations completed
            research_id: Research ID for reference

        Returns:
            str: Formatted error report in Markdown
        """
        try:
            # Analyze the error
            context = {
                "query": query,
                "search_iterations": search_iterations,
                "research_id": research_id,
                "partial_results": partial_results,
            }

            if partial_results:
                context.update(partial_results)

            error_analysis = self.error_reporter.analyze_error(
                error_message, context
            )

            # Build the simplified report
            report_parts = []

            # Header with user-friendly error message and logs reference
            user_friendly_message = self._make_error_user_friendly(
                error_message
            )
            category_title = error_analysis.get("title", "Error")

            report_parts.append("# ⚠️ Research Failed")
            report_parts.append(f"\n**Error Type:** {category_title}")
            report_parts.append(f"\n**What happened:** {user_friendly_message}")
            report_parts.append(
                '\n*For detailed error information, scroll down to the research logs and select "Errors" from the filter.*'
            )

            # Support links - moved up for better visibility
            report_parts.append("\n## 💬 Get Help")
            report_parts.append("We're here to help you get this working:")
            report_parts.append(
                "- 📖 **Documentation & guides:** [Wiki](https://github.com/LearningCircuit/local-deep-research/wiki)"
            )
            report_parts.append(
                "- 💬 **Chat with the community:** [Discord #help-and-support](https://discord.gg/ttcqQeFcJ3)"
            )
            report_parts.append(
                "- 🐛 **Report bugs or get help:** [GitHub Issues](https://github.com/LearningCircuit/local-deep-research/issues) *(don't hesitate to ask if you're stuck!)*"
            )
            report_parts.append(
                "- 💭 **Join discussions:** [Reddit r/LocalDeepResearch](https://www.reddit.com/r/LocalDeepResearch/) *(checked less frequently)*"
            )

            # Show partial results if available (in expandable section)
            if error_analysis.get("has_partial_results"):
                partial_content = self._format_partial_results(partial_results)
                if partial_content:
                    report_parts.append(
                        f"\n<details>\n<summary>📊 Partial Results Available</summary>\n\n{partial_content}\n</details>"
                    )

            return "\n".join(report_parts)

        except Exception:
            # Fallback: always return something, even if error report generation fails
            logger.exception("Failed to generate error report")
            return f"""# ⚠️ Research Failed

**What happened:** {error_message}

## 💬 Get Help
We're here to help you get this working:
- 📖 **Documentation & guides:** [Wiki](https://github.com/LearningCircuit/local-deep-research/wiki)
- 💬 **Chat with the community:** [Discord #help-and-support](https://discord.gg/ttcqQeFcJ3)
- 🐛 **Report bugs or get help:** [GitHub Issues](https://github.com/LearningCircuit/local-deep-research/issues) *(don't hesitate to ask if you're stuck!)*

*Note: Error report generation failed - showing basic error information.*"""

    def _format_partial_results(
        self, partial_results: Optional[Dict[str, Any]]
    ) -> str:
        """
        Format partial results for display

        Args:
            partial_results: Partial results data

        Returns:
            str: Formatted partial results
        """
        if not partial_results:
            return ""

        formatted_parts = []

        # Current knowledge summary
        if "current_knowledge" in partial_results:
            knowledge = partial_results["current_knowledge"]
            if knowledge and len(knowledge.strip()) > 50:
                formatted_parts.append("### Research Summary\n")
                formatted_parts.append(
                    knowledge[:1000] + "..."
                    if len(knowledge) > 1000
                    else knowledge
                )
                formatted_parts.append("")

        # Search results
        if "search_results" in partial_results:
            results = partial_results["search_results"]
            if results:
                formatted_parts.append("### Search Results Found\n")
                for i, result in enumerate(results[:5], 1):  # Show top 5
                    title = result.get("title", "Untitled")
                    url = result.get("url", "")
                    formatted_parts.append(f"{i}. **{title}**")
                    if url:
                        formatted_parts.append(f"   - URL: {url}")
                formatted_parts.append("")

        # Findings
        if "findings" in partial_results:
            findings = partial_results["findings"]
            if findings:
                formatted_parts.append("### Research Findings\n")
                for i, finding in enumerate(findings[:3], 1):  # Show top 3
                    content = finding.get("content", "")
                    if content and not content.startswith("Error:"):
                        phase = finding.get("phase", f"Finding {i}")
                        formatted_parts.append(f"**{phase}:**")
                        formatted_parts.append(
                            content[:500] + "..."
                            if len(content) > 500
                            else content
                        )
                        formatted_parts.append("")

        if formatted_parts:
            formatted_parts.append(
                "*Note: The above results were successfully collected before the error occurred.*"
            )

        return "\n".join(formatted_parts) if formatted_parts else ""

    def _get_technical_context(
        self,
        error_analysis: Dict[str, Any],
        partial_results: Optional[Dict[str, Any]],
    ) -> str:
        """
        Get additional technical context for the error

        Args:
            error_analysis: Error analysis results
            partial_results: Partial results if available

        Returns:
            str: Technical context information
        """
        context_parts = []

        # Add timing information if available
        if partial_results:
            if "start_time" in partial_results:
                context_parts.append(
                    f"- **Start Time:** {partial_results['start_time']}"
                )

            if "last_activity" in partial_results:
                context_parts.append(
                    f"- **Last Activity:** {partial_results['last_activity']}"
                )

            # Add model information
            if "model_config" in partial_results:
                config = partial_results["model_config"]
                context_parts.append(
                    f"- **Model:** {config.get('model_name', 'Unknown')}"
                )
                context_parts.append(
                    f"- **Provider:** {config.get('provider', 'Unknown')}"
                )

            # Add search information
            if "search_config" in partial_results:
                search_config = partial_results["search_config"]
                context_parts.append(
                    f"- **Search Engine:** {search_config.get('engine', 'Unknown')}"
                )
                context_parts.append(
                    f"- **Max Results:** {search_config.get('max_results', 'Unknown')}"
                )

            # Add any error codes or HTTP status
            if "status_code" in partial_results:
                context_parts.append(
                    f"- **Status Code:** {partial_results['status_code']}"
                )

            if "error_code" in partial_results:
                context_parts.append(
                    f"- **Error Code:** {partial_results['error_code']}"
                )

        # Add error-specific context based on category
        category = error_analysis.get("category")
        if category:
            if "connection" in category.value.lower():
                context_parts.append(
                    "- **Network Error:** Connection-related issue detected"
                )
                context_parts.append(
                    "- **Retry Recommended:** Check service status and try again"
                )
            elif "model" in category.value.lower():
                context_parts.append(
                    "- **Model Error:** Issue with AI model or configuration"
                )
                context_parts.append(
                    "- **Check:** Model service availability and parameters"
                )

        return "\n".join(context_parts) if context_parts else ""

    def generate_quick_error_summary(
        self, error_message: str
    ) -> Dict[str, str]:
        """
        Generate a quick error summary for API responses

        Args:
            error_message: The error message

        Returns:
            dict: Quick error summary
        """
        error_analysis = self.error_reporter.analyze_error(error_message)

        return {
            "title": error_analysis["title"],
            "category": error_analysis["category"].value,
            "severity": error_analysis["severity"],
            "recoverable": error_analysis["recoverable"],
        }

    def _make_error_user_friendly(self, error_message: str) -> str:
        """
        Replace cryptic technical error messages with user-friendly versions

        Args:
            error_message: The original technical error message

        Returns:
            str: User-friendly error message, or original if no replacement found
        """
        # Messages carrying a SPECIFIC "(Error type: <code>)" token have already
        # been classified and rewritten into user-friendly text by upstream code
        # (openai_compat_errors, the Ollama/status-code branches in
        # research_service). Return those unchanged -- the regex patterns below
        # are meant for raw, unrewritten exceptions only, and would otherwise
        # clobber the tailored upstream message (e.g. the openai_connection_refused
        # message getting overwritten by the generic "Connection refused" hint).
        #
        # "(Error type: unknown)", however, is attached to a RAW exception string
        # that upstream could NOT classify (research_service appends the token to
        # str(exc) verbatim). Let those fall through to the replacement table
        # below -- it acts as a second-chance classifier and can only add help to
        # an otherwise-cryptic message, never clobber friendly text.
        if (
            re.search(r"\(Error type: \w+\)", error_message)
            and "(Error type: unknown)" not in error_message
        ):
            return error_message
        # Dictionary of technical errors to user-friendly messages
        error_replacements = {
            "max_workers must be greater than 0": (
                "The LLM failed to generate search questions. This usually means the LLM service isn't responding properly.\n\n"
                "**Try this:**\n"
                "- Check if your LLM service (Ollama/LM Studio) is running\n"
                "- Restart the LLM service\n"
                "- Try a different model"
            ),
            "POST predict.*EOF": (
                "Lost connection to Ollama. This usually means Ollama stopped responding or there's a network issue.\n\n"
                "**Try this:**\n"
                "- Restart Ollama: `ollama serve`\n"
                "- Check if Ollama is still running: `ps aux | grep ollama`\n"
                "- Try a different port if 11434 is in use"
            ),
            "HTTP error 404.*research results": (
                "The research completed but the results can't be displayed. The files were likely generated successfully.\n\n"
                "**Try this:**\n"
                "- Check the `research_outputs` folder for your report\n"
                "- Ensure the folder has proper read/write permissions\n"
                "- Restart the LDR web interface"
            ),
            "Connection refused|\\[Errno 111\\]": (
                "Cannot connect to the LLM service. The service might not be running or is using a different address.\n\n"
                "**Try this:**\n"
                "- Start your LLM service (Ollama: `ollama serve`, LM Studio: launch the app)\n"
                "- **Docker on Mac/Windows:** Change URL from `http://localhost:1234` to `http://host.docker.internal:1234`\n"
                "- **Docker on Linux:** Use your host IP instead of localhost (find with `hostname -I`)\n"
                "- Check the service URL in settings matches where your LLM is running\n"
                "- Verify the port number is correct (Ollama: 11434, LM Studio: 1234)"
            ),
            "The search is longer than 256 characters": (
                "Your search query is too long for GitHub's API (max 256 characters).\n\n"
                "**Try this:**\n"
                "- Shorten your research query\n"
                "- Use a different search engine (DuckDuckGo, Searx, etc.)\n"
                "- Break your research into smaller, focused queries"
            ),
            "No module named.*local_deep_research": (
                "Installation issue detected. The package isn't properly installed.\n\n"
                "**Try this:**\n"
                "- Reinstall: `pip install -e .` from the project directory\n"
                "- Check you're using the right Python environment\n"
                "- For Docker users: rebuild the container"
            ),
            "Failed to create search engine|search engine.*could not be found": (
                "Search engine configuration problem.\n\n"
                "**Try this:**\n"
                "- Use the default search engine (auto)\n"
                "- Check search engine settings in Advanced Options\n"
                "- Ensure required API keys are set for external search engines"
            ),
            "No search results found|All search engines.*blocked.*rate.*limited": (
                "No search results were found for your query. This could mean all search engines are unavailable.\n\n"
                "**Try this:**\n"
                "- **If using SearXNG:** Check if your SearXNG Docker container is running: `docker ps`\n"
                "- **Start SearXNG:** `docker run -d -p 8080:8080 searxng/searxng` then set URL to `http://localhost:8080`\n"
                "- **Try different search terms:** Use broader, more general keywords\n"
                "- **Check network connection:** Ensure you can access the internet\n"
                "- **Switch search engines:** Try DuckDuckGo, Brave, or Google (if API key configured)\n"
                "- **Check for typos** in your research query"
            ),
            "TypeError.*Context.*Size|'<' not supported between instances of .* and 'NoneType'": (
                "Model configuration issue. The context size setting might not be compatible with your model.\n\n"
                "**Try this:**\n"
                "- Check your model's maximum context size\n"
                "- Leave context size settings at default\n"
                "- Try a different model"
            ),
            "Model.*not found in Ollama": (
                "The specified model isn't available in Ollama.\n\n"
                "**Try this:**\n"
                "- Check available models: `ollama list`\n"
                "- Pull the model: `ollama pull <model-name>`\n"
                "- Use the exact model name shown in `ollama list` (e.g., 'gemma2:9b' not 'gemma:latest')"
            ),
            "No auth credentials found|401.*API key": (
                "API key is missing or incorrectly configured.\n\n"
                "**Try this:**\n"
                "- Set API key in the web UI settings (not in .env files)\n"
                "- Go to Settings → Advanced → enter your API key\n"
                "- For custom endpoints, ensure the key format matches what your provider expects"
            ),
            "Attempt to write readonly database": (
                "Permission issue with the database file.\n\n"
                "**Try this:**\n"
                "- On Windows: Run as Administrator\n"
                "- On Linux/Mac: Check folder permissions\n"
                "- Delete and recreate the database file if corrupted"
            ),
            "database is locked|database table is locked": (
                "The local database is temporarily locked, usually because "
                "another operation is writing to it at the same time.\n\n"
                "**Try this:**\n"
                "- Wait a few seconds and run the research again\n"
                "- Avoid starting multiple researches that write at the same moment\n"
                "- If it persists, restart the LDR server to clear stuck connections"
            ),
            "does not support tools|tool calling": (
                "Your current model does not support tool calling, which is required by the **langgraph-agent** strategy.\n\n"
                "**Try this:**\n"
                "- **Switch strategy:** Go to Settings → Search Strategy and select **source-based** or **focused-iteration** instead\n"
                "- **Switch model:** Use a model that supports tool calling (e.g., qwen3, gpt-oss:20b, mistral, llama3.1)\n"
                "- The langgraph-agent strategy requires models with native tool/function calling support"
            ),
            "object has no attribute 'model_dump'": (
                "The agent received tool calls in a shape LangChain could not parse into structured messages. "
                "This is a known failure mode of the **langgraph-agent** strategy when the LLM, the OpenAI-compatible server, "
                "or a proxy in front of either of them produces non-standard tool-call responses.\n\n"
                "**Try this:**\n"
                "- **Bypass any proxy/shim** sitting in front of your local LLM server. Tool-call schema translation is the single most common place OpenAI-compatible proxies break, especially when they inject vendor extension fields (e.g. llama.cpp's `timings_per_token`, `return_progress`).\n"
                "- **Update your serving stack.** llama.cpp has documented OpenAI tool-call format bugs (e.g. arguments emitted as object instead of JSON string). Recent builds of llama.cpp / vLLM / Ollama fix many of these.\n"
                "- **Use a model with native tool-calling support.** Models without an explicit tool-call template fall back to generic prompting, which frequently produces unparseable outputs.\n"
                "- **Switch strategy.** If you don't need an agentic loop, Settings → Search Strategy → **source-based** or **focused-iteration** sidesteps tool-calling entirely.\n"
                "- See [issue #3897](https://github.com/LearningCircuit/local-deep-research/issues/3897) for the original report and discussion."
            ),
            "Invalid value.*SearXNG": (
                "SearXNG configuration or rate limiting issue.\n\n"
                "**Try this:**\n"
                "- Keep 'Search snippets only' enabled (don't turn it off)\n"
                "- Restart SearXNG: `docker restart searxng`\n"
                "- If rate limited, wait a few minutes or use a VPN"
            ),
            "host.*localhost.*Docker|127\\.0\\.0\\.1.*Docker|localhost.*1234.*Docker|LM.*Studio.*Docker.*Mac": (
                "Docker networking issue - can't connect to services on host.\n\n"
                "**Try this:**\n"
                "- **On Mac/Windows Docker:** Replace 'localhost' or '127.0.0.1' with 'host.docker.internal'\n"
                "- **On Linux Docker:** Use your host's actual IP address (find with `hostname -I`)\n"
                "- **Example:** Change `http://localhost:1234` to `http://host.docker.internal:1234`\n"
                "- Ensure the service port isn't blocked by firewall\n"
                "- Alternative: Use host networking mode (see wiki for setup)"
            ),
        }

        # Check each pattern and replace if found
        for pattern, replacement in error_replacements.items():
            if re.search(pattern, error_message, re.IGNORECASE):
                return f"{replacement}\n\nTechnical error: {error_message}"

        # If no specific replacement found, return original message
        return error_message
