"""
GoalDecomposer — Mission-Driven Task Planning

Translates the bot's `mission` config + `plugins` capabilities into
concrete, weighted Task objects. Pure logic — no LLM, no device,
no network, no side effects.

This is the bridge between:
  - "What does the user WANT?" (mission.strategy)
  - "What CAN the bot DO?" (enabled plugins + actions)
  - "What SHOULD it do NOW?" (weighted Task selection)

Tesla analogy: FSD doesn't have a "goal: drive safely" config.
It derives behavior from destination + road rules + sensor capabilities.
"""

import logging
import random
from dataclasses import dataclass
from typing import Dict, List

logger = logging.getLogger(__name__)

# ── Strategy Weight Tables ──
# Each strategy defines relative weights for screen targets.
# Higher weight = more likely to be selected by GrowthBrain.
STRATEGY_WEIGHTS: Dict[str, Dict[str, float]] = {
    "aggressive_growth": {
        "HomeFeed": 0.15,
        "ExploreFeed": 0.45,
        "ReelsFeed": 0.15,
        "StoriesFeed": 0.10,
        "MessageInbox": 0.10,
        "FollowingList": 0.05,
    },
    "community_builder": {
        "HomeFeed": 0.40,
        "ExploreFeed": 0.10,
        "ReelsFeed": 0.05,
        "StoriesFeed": 0.25,
        "MessageInbox": 0.15,
        "FollowingList": 0.05,
    },
    "passive_learning": {
        "HomeFeed": 0.20,
        "ExploreFeed": 0.50,
        "ReelsFeed": 0.20,
        "StoriesFeed": 0.05,
        "MessageInbox": 0.00,
        "FollowingList": 0.05,
    },
    "stealth_lurker": {
        "HomeFeed": 0.35,
        "ExploreFeed": 0.25,
        "ReelsFeed": 0.15,
        "StoriesFeed": 0.15,
        "MessageInbox": 0.05,
        "FollowingList": 0.05,
    },
}

# ── Plugin → Screen Mapping ──
# Which plugins enable which screen targets.
# A screen is only viable if at least one enabling plugin is active.
# Some plugins work on MULTIPLE screens (likes work on home, explore, reels).
PLUGIN_SCREENS_MAP: Dict[str, set] = {
    "likes": {"HomeFeed", "ExploreFeed", "ReelsFeed"},
    "comment": {"HomeFeed", "ExploreFeed"},
    "follow": {"HomeFeed", "ExploreFeed"},
    "repost": {"HomeFeed", "ExploreFeed"},
    "profile_visit": {"HomeFeed", "ExploreFeed"},
    "grid_like": {"HomeFeed"},
    "carousel_browsing": {"HomeFeed"},
    "rabbit_hole": {"HomeFeed", "ExploreFeed"},
    "story_view": {"StoriesFeed"},
    "dm_reply": {"MessageInbox"},
}

# ── Action → Screen Mapping ──
# The `actions:` config section maps directly to screens.
ACTION_SCREEN_MAP: Dict[str, str] = {
    "feed": "HomeFeed",
    "explore": "ExploreFeed",
    "reels": "ReelsFeed",
}

# ── Screen → Verb Mapping ──
SCREEN_VERB_MAP: Dict[str, str] = {
    "HomeFeed": "browse_feed",
    "ExploreFeed": "browse_explore",
    "ReelsFeed": "browse_reels",
    "StoriesFeed": "view_stories",
    "MessageInbox": "check_messages",
    "FollowingList": "manage_following",
}

# ── Screen → Human Intent ──
SCREEN_INTENT_MAP: Dict[str, str] = {
    "HomeFeed": "Interact with posts in the home feed",
    "ExploreFeed": "Discover and engage with new content",
    "ReelsFeed": "Browse and interact with reels",
    "StoriesFeed": "View and react to stories",
    "MessageInbox": "Reply to unread direct messages",
    "FollowingList": "Review and manage following list",
}

DEFAULT_BUDGET = 5


@dataclass(frozen=True)
class Task:
    """A concrete, executable unit of work for the bot.

    Unlike abstract goals ("nurture community"), a Task has:
    - A specific screen to navigate to
    - A measurable budget (how many posts/items to process)
    - A weight for probabilistic selection
    - A human-readable intent for logging
    """

    verb: str
    target_screen: str
    intent: str
    budget_posts: int
    weight: float


class GoalDecomposer:
    """Translates mission + plugins → weighted Task list.

    Pure logic, zero side effects. Call generate_tasks() to get
    the bot's action menu for the current session.
    """

    def __init__(
        self,
        plugins: Dict[str, dict],
        actions: Dict[str, str],
        mission: Dict[str, str],
    ):
        self._plugins = plugins
        self._actions = actions
        self._strategy = mission.get("strategy", "aggressive_growth")

    def generate_tasks(self) -> List[Task]:
        """Generate weighted tasks from config.

        Returns an empty list if no plugins are enabled —
        the bot literally has nothing to do.
        """
        viable_screens = self._discover_viable_screens()
        if not viable_screens:
            return []

        strategy_weights = STRATEGY_WEIGHTS.get(self._strategy, STRATEGY_WEIGHTS["aggressive_growth"])

        tasks = []
        for screen in viable_screens:
            weight = strategy_weights.get(screen, 0.1)
            if weight <= 0:
                continue

            budget = self._budget_for_screen(screen)
            verb = SCREEN_VERB_MAP.get(screen, "browse")
            intent = SCREEN_INTENT_MAP.get(screen, f"Interact on {screen}")

            tasks.append(
                Task(
                    verb=verb,
                    target_screen=screen,
                    intent=intent,
                    budget_posts=budget,
                    weight=weight,
                )
            )

        return tasks

    def _discover_viable_screens(self) -> set:
        """Determine which screens the bot can meaningfully interact on.

        A screen is viable if it has BOTH:
        1. A route (action config or plugin-implied), AND
        2. At least one active plugin that can DO something there.

        Without an active plugin, navigating to a screen is pointless —
        the bot would just scroll with nothing to interact on.
        """
        # 1. Collect screens with active plugins
        plugin_screens: set = set()
        for plugin_name, screens in PLUGIN_SCREENS_MAP.items():
            plugin_cfg = self._plugins.get(plugin_name, {})
            if not plugin_cfg:
                continue
            if not self._is_plugin_active(plugin_cfg):
                continue
            plugin_screens.update(screens)

        # 2. Screens from actions are only viable if plugins exist for them
        action_screens: set = set()
        for action_key, screen in ACTION_SCREEN_MAP.items():
            if action_key in self._actions and self._actions[action_key]:
                action_screens.add(screen)

        # 3. A screen must have plugin coverage to be viable
        # Action-enabled screens need at least one active plugin
        viable = action_screens & plugin_screens

        # 4. Plugin-only screens (story_view, dm_reply) are viable
        #    even without an explicit action config
        viable |= plugin_screens

        return viable

    def _is_plugin_active(self, plugin_cfg: dict) -> bool:
        """Check if a plugin config represents an active plugin.

        A plugin is active if:
        - It has `enabled: true` (explicit), OR
        - It has `percentage` > 0 (implicit enable), OR
        - It has any config keys and `enabled` is not explicitly False
        """
        # Explicit disable
        if plugin_cfg.get("enabled") is False:
            return False

        # Explicit enable
        if plugin_cfg.get("enabled") is True:
            return True

        # Percentage-based: 0% means disabled
        pct = plugin_cfg.get("percentage")
        if pct is not None:
            try:
                return float(pct) > 0
            except (ValueError, TypeError):
                return False

        # Has config keys but no explicit enabled/percentage = active
        return bool(plugin_cfg)

    def _budget_for_screen(self, screen: str) -> int:
        """Determine the post budget for a screen.

        Reads from actions config (e.g. feed: "5-10") and parses
        the range string into a random integer within bounds.
        """
        # Map screen back to action key
        reverse_map = {v: k for k, v in ACTION_SCREEN_MAP.items()}
        action_key = reverse_map.get(screen)

        if action_key and action_key in self._actions:
            return _parse_range(self._actions[action_key])

        # Special screens get fixed budgets from plugin config
        if screen == "StoriesFeed":
            story_cfg = self._plugins.get("story_view", {})
            count_str = story_cfg.get("count", "1-3")
            return _parse_range(str(count_str))

        if screen == "MessageInbox":
            return DEFAULT_BUDGET

        return DEFAULT_BUDGET


def _parse_range(range_str: str) -> int:
    """Parse a range string like '5-10' into a random int within bounds."""
    try:
        if "-" in str(range_str):
            parts = str(range_str).split("-")
            low, high = int(parts[0]), int(parts[1])
            return random.randint(low, high)
        return int(range_str)
    except (ValueError, IndexError):
        return DEFAULT_BUDGET
