"""
News analyzer that produces modular output components.
Breaks down news analysis into separate, reusable pieces.
"""

from typing import List, Dict, Any, Optional, cast
from datetime import datetime, timezone, UTC
from loguru import logger

from .utils import generate_card_id
from ..utils.topic_generator import generate_topics
from ...config.llm_config import get_llm
from ...utilities.json_utils import extract_json, get_llm_response_text


class NewsAnalyzer:
    """
    Analyzes news search results to produce modular components.

    Instead of one big analysis, produces:
    - News items table
    - Big picture summary
    - Watch for (next 24-48h)
    - Pattern recognition
    - Extractable topics for subscriptions
    """

    def __init__(
        self,
        llm_client: Optional[Any] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
    ):
        """
        Initialize the news analyzer.

        Args:
            llm_client: LLM client for analysis
            settings_snapshot: Optional saved settings snapshot so the
                LLM is constructed under the user's egress policy. The
                news scheduler runs in background threads without a
                live request context, so without an explicit snapshot
                the PEP guard at the top of ``get_llm`` skips and a
                cloud LLM can fire under require_local_endpoint.
        """
        self._owns_llm = llm_client is None
        self.llm_client = llm_client or get_llm(
            settings_snapshot=settings_snapshot
        )

    def close(self) -> None:
        """Close the LLM client if this instance created it."""
        from ...utilities.resource_utils import safe_close

        if self._owns_llm:
            safe_close(self.llm_client, "news analyzer LLM")

    def analyze_news(
        self, search_results: List[Dict[str, Any]]
    ) -> Dict[str, Any]:
        """
        Analyze news search results into modular components.

        Args:
            search_results: Raw search results

        Returns:
            Dictionary with modular analysis components
        """
        if not search_results:
            return self._empty_analysis()

        try:
            # Step 1: Extract news items table
            logger.debug("Extracting news items")
            news_items = self.extract_news_items(search_results)

            # Step 2: Generate overview components (separate LLM calls for modularity)
            logger.debug("Generating analysis components")
            components = {
                "items": news_items,
                "item_count": len(news_items),
                "search_result_count": len(search_results),
                "timestamp": datetime.now(timezone.utc).isoformat(),
            }

            if news_items:
                # Each component is generated independently
                components["big_picture"] = self.generate_big_picture(
                    news_items
                )
                components["watch_for"] = self.generate_watch_for(news_items)
                components["patterns"] = self.generate_patterns(news_items)
                components["topics"] = self.extract_topics(news_items)
                components["categories"] = self._count_categories(news_items)
                components["impact_summary"] = self._summarize_impact(
                    news_items
                )

            topics_list = cast(List[Any], components.get("topics", []))
            logger.info(
                f"News analysis complete: {len(news_items)} items, {len(topics_list)} topics"
            )
            return components

        except Exception:
            logger.exception("Error analyzing news")
            return self._empty_analysis()

    def extract_news_items(
        self, search_results: List[Dict[str, Any]], max_items: int = 10
    ) -> List[Dict[str, Any]]:
        """
        Extract structured news items from search results.

        Args:
            search_results: Raw search results
            max_items: Maximum number of items to extract

        Returns:
            List of structured news items
        """
        if not self.llm_client:
            logger.warning("No LLM client available for news extraction")
            return []

        # Prepare search results for LLM
        snippets = self._prepare_snippets(
            search_results  # Use all results, let LLM handle token limits
        )

        prompt = f"""
Extract up to {max_items} important news stories from these search results.
Today's date: {datetime.now(UTC).strftime("%B %d, %Y")}

{snippets}

For each news story, extract:
1. headline - 8 words max describing the story
2. category - A descriptive category for this news (be specific, not limited to generic categories)
3. summary - 3 clear sentences about what happened
4. impact_score - 1-10 based on significance
5. source_url - URL from the search results
6. entities - people, places, organizations mentioned
7. is_developing - true/false if story is still developing
8. time_ago - when it happened (2 hours ago, yesterday, etc)

Return as JSON array of news items.
Focus on genuinely newsworthy stories.
"""

        try:
            response = self.llm_client.invoke(prompt)
            content = get_llm_response_text(response)

            # Parse JSON response
            news_items = extract_json(content, expected_type=list)
            if news_items is not None:
                # Validate and clean items
                valid_items = []
                for item in news_items[:max_items]:
                    if self._validate_news_item(item):
                        # Generate ID
                        item["id"] = generate_card_id()
                        valid_items.append(item)

                return valid_items

        except Exception:
            logger.exception("Error extracting news items")

        return []

    def generate_big_picture(self, news_items: List[Dict[str, Any]]) -> str:
        """
        Generate the big picture summary of how events connect.

        Args:
            news_items: Extracted news items

        Returns:
            Big picture summary (3-4 sentences)
        """
        if not self.llm_client or not news_items:
            return ""

        # Prepare news summaries
        summaries = "\n".join(
            [
                f"- {item['headline']}: {item.get('summary', '')[:100]}..."
                for item in news_items[:10]
            ]
        )

        prompt = f"""
Based on these news stories, write THE BIG PICTURE summary.
Connect the dots between events. What's the larger narrative?
Write 3-4 sentences maximum.

News stories:
{summaries}

THE BIG PICTURE:"""

        try:
            response = self.llm_client.invoke(prompt)
            content = (
                response.content
                if hasattr(response, "content")
                else str(response)
            )
            return content.strip()
        except Exception:
            logger.exception("Error generating big picture")
            return ""

    def generate_watch_for(self, news_items: List[Dict[str, Any]]) -> List[str]:
        """
        Generate list of developments to watch for in next 24-48 hours.

        Args:
            news_items: Extracted news items

        Returns:
            List of bullet points
        """
        if not self.llm_client or not news_items:
            return []

        # Focus on developing stories
        developing = [
            item for item in news_items if item.get("is_developing", False)
        ]
        if not developing:
            developing = news_items[:5]

        summaries = "\n".join(
            [
                f"- {item['headline']}: {item.get('summary', '')[:100]}..."
                for item in developing
            ]
        )

        prompt = f"""
Based on these developing news stories, what should we watch for in the next 24-48 hours?
Write 3-5 specific, actionable items.

Developing stories:
{summaries}

WATCH FOR:
-"""

        try:
            response = self.llm_client.invoke(prompt)
            content = (
                response.content
                if hasattr(response, "content")
                else str(response)
            )

            # Parse bullet points
            lines = content.strip().split("\n")
            watch_items = []
            for line in lines:
                line = line.strip()
                if line and line not in ["WATCH FOR:", "Watch for:"]:
                    # Remove bullet markers
                    line = line.lstrip("-•* ")
                    if line:
                        watch_items.append(line)

            return watch_items[:5]

        except Exception:
            logger.exception("Error generating watch items")
            return []

    def generate_patterns(self, news_items: List[Dict[str, Any]]) -> str:
        """
        Identify emerging patterns from today's news.

        Args:
            news_items: Extracted news items

        Returns:
            Pattern recognition summary
        """
        if not self.llm_client or not news_items:
            return ""

        # Group by category
        by_category: Dict[str, List[Any]] = {}
        for item in news_items:
            cat = item.get("category", "Other")
            if cat not in by_category:
                by_category[cat] = []
            by_category[cat].append(item["headline"])

        category_summary = "\n".join(
            [
                f"{cat}: {len(items)} stories"
                for cat, items in by_category.items()
            ]
        )

        prompt = f"""
Identify emerging patterns from today's news distribution:

{category_summary}

Top headlines:
{chr(10).join([f"- {item['headline']}" for item in news_items[:10]])}

PATTERN RECOGNITION (1-2 sentences):"""

        try:
            response = self.llm_client.invoke(prompt)
            content = (
                response.content
                if hasattr(response, "content")
                else str(response)
            )
            return content.strip()
        except Exception:
            logger.exception("Error generating patterns")
            return ""

    def extract_topics(
        self, news_items: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        Extract subscribable topics from news items.

        Args:
            news_items: Extracted news items

        Returns:
            List of topic dictionaries with metadata
        """
        topics = []

        # Use topic generator to extract from each item
        for item in news_items:
            # Use topic generator with headline as query and summary as findings
            headline = item.get("headline", "")
            summary = item.get("summary", "")
            category = item.get("category", "")

            extracted = generate_topics(
                query=headline,
                findings=summary,
                category=category,
                max_topics=3,
            )

            for topic in extracted:
                topics.append(
                    {
                        "name": topic,
                        "source_item_id": item.get("id"),
                        "source_headline": item.get("headline"),
                        "category": item.get("category"),
                        "impact_score": item.get("impact_score", 5),
                    }
                )

        # Deduplicate and sort by frequency
        topic_counts = {}
        topic_metadata = {}

        for topic_info in topics:
            name = topic_info["name"]
            if name not in topic_counts:
                topic_counts[name] = 0
                topic_metadata[name] = topic_info
            topic_counts[name] += 1

            # Keep highest impact score
            if (
                topic_info["impact_score"]
                > topic_metadata[name]["impact_score"]
            ):
                topic_metadata[name] = topic_info

        # Create final topic list
        final_topics = []
        for topic, count in sorted(
            topic_counts.items(), key=lambda x: x[1], reverse=True
        ):
            metadata = topic_metadata[topic]
            metadata["frequency"] = count
            metadata["query"] = f"{topic} latest developments news"
            final_topics.append(metadata)

        return final_topics[:10]  # Top 10 topics

    def _prepare_snippets(self, search_results: List[Dict[str, Any]]) -> str:
        """Prepare search result snippets for LLM processing."""
        snippets = []
        for i, result in enumerate(search_results):
            snippet = f"[{i + 1}] "
            if result.get("title"):
                snippet += f"Title: {result['title']}\n"
            if result.get("url"):
                snippet += f"URL: {result['url']}\n"
            if result.get("snippet"):
                snippet += f"Snippet: {result['snippet'][:200]}...\n"
            elif result.get("content"):
                snippet += f"Content: {result['content'][:200]}...\n"

            snippets.append(snippet)

        return "\n".join(snippets)

    def _validate_news_item(self, item: Dict[str, Any]) -> bool:
        """Validate that a news item has required fields."""
        required = ["headline", "summary"]
        return all(field in item and item[field] for field in required)

    def _count_categories(
        self, news_items: List[Dict[str, Any]]
    ) -> Dict[str, int]:
        """Count items by category."""
        counts: Dict[str, int] = {}
        for item in news_items:
            cat = item.get("category", "Other")
            counts[cat] = counts.get(cat, 0) + 1
        return counts

    def _summarize_impact(
        self, news_items: List[Dict[str, Any]]
    ) -> Dict[str, Any]:
        """Summarize impact scores."""
        if not news_items:
            return {"average": 0, "high_impact_count": 0}

        scores = [item.get("impact_score", 5) for item in news_items]
        return {
            "average": sum(scores) / len(scores),
            "high_impact_count": len([s for s in scores if s >= 8]),
            "max": max(scores),
            "min": min(scores),
        }

    def _empty_analysis(self) -> Dict[str, Any]:
        """Return empty analysis structure."""
        return {
            "items": [],
            "item_count": 0,
            "big_picture": "",
            "watch_for": [],
            "patterns": "",
            "topics": [],
            "categories": {},
            "impact_summary": {"average": 0, "high_impact_count": 0},
            "timestamp": datetime.now(timezone.utc).isoformat(),
        }
