import logging
import os
import sys
from datetime import datetime
from typing import Optional

import configargparse
import yaml
from colorama import Fore, Style

logger = logging.getLogger(__name__)


class Config:
    def __init__(self, first_run=False, **kwargs):
        if kwargs:
            self.args = kwargs
            self.module = True
        else:
            self.args = list(sys.argv)
            self.module = False

        if not self.module and "--config" not in self.args:
            if os.path.exists("config.yml"):
                self.args.extend(["--config", "config.yml"])
        self.config = None
        self.config_list = None
        self.actions = {}
        self.enabled = []
        self.unknown_args = []
        self.debug = False
        self.device_id: Optional[str] = None
        self.app_id: Optional[str] = None
        self.first_run = first_run
        self.username = False

        # Pre-Load Variables Needed for Script Init
        if self.module:
            if "debug" in self.args:
                self.debug = True
            if "username" in self.args:
                self.username = self.args["username"]
                if isinstance(self.username, list) and len(self.username) > 0:
                    self.username = self.username[0]
            if "app_id" in self.args:
                app_id = self.args["app_id"]
                if app_id:
                    self.app_id = app_id
                else:
                    self.app_id = "com.instagram.android"
        elif "--config" in self.args:
            try:
                file_name = self.args[self.args.index("--config") + 1]
                if not file_name.endswith((".yml", ".yaml")):
                    logger.error(
                        f"You have to specify a *.yml / *.yaml config file path (For example 'accounts/your_account_name/config.yml')! \nYou entered: {file_name}, abort."
                    )
                    sys.exit(1)
                logger.debug(get_time_last_save(file_name))
                with open(file_name, encoding="utf-8") as fin:
                    # preserve order of yaml
                    self.config_list = [line.strip() for line in fin]
                    fin.seek(0)
                    # preload config for debug and username
                    self.config = yaml.safe_load(fin)
            except IndexError:
                logger.warning(
                    "Please provide a filename with your --config argument. Example: '--config accounts/yourusername/config.yml'"
                )
                exit(2)
            except FileNotFoundError:
                logger.error(
                    f"I can't see the file '{file_name}'! Double check the spelling or if you're calling the bot from the right folder. (You're there: '{os.getcwd()}')"
                )
                exit(2)

            self.username = self.config.get("username", False)
            if isinstance(self.username, list) and len(self.username) > 0:
                self.username = self.username[0]
            self.debug = self.config.get("debug", False)
            self.app_id = self.config.get("app_id", "com.instagram.android")

            # Autonomous goals removed — the bot now derives tasks from mission + plugins
            # via GoalDecomposer. See GramAddict/core/goal_decomposer.py.
        else:
            if "--debug" in self.args:
                self.debug = True
            if "--username" in self.args:
                try:
                    self.username = self.args[self.args.index("--username") + 1]
                except IndexError:
                    logger.warning(
                        "Please provide a username with your --username argument. Example: '--username yourusername'"
                    )
                    exit(2)
            if "--app-id" in self.args:
                self.app_id = self.args[self.args.index("--app-id") + 1]
            else:
                self.app_id = "com.instagram.android"

        # Configure ArgParse
        self.parser = configargparse.ArgumentParser(
            config_file_open_func=lambda filename: open(filename, "r+", encoding="utf-8"),
            description="GramAddict Instagram Bot",
        )
        self.parser.add_argument(
            "--config",
            required=False,
            help="config file path",
        )
        self.parser.add_argument(
            "--device",
            help="device id",
        )
        self.parser.add_argument(
            "--app-id",
            help="app id",
            default="com.instagram.android",
        )
        self.parser.add_argument(
            "--debug",
            action="store_true",
            help="debug mode",
        )
        self.parser.add_argument(
            "--shadow-mode",
            required=False,
            action="store_true",
            help="Enable Tesla E2E Vision 'Shadow Mode' Telemetry daemon.",
        )

        # Core Singularity Jobs
        self.parser.add_argument("--feed", help="Amount of feed posts to interact with", default=None)
        self.parser.add_argument("--explore", help="Amount of explore posts to interact with", default=None)
        self.parser.add_argument("--reels", help="Amount of reels to interact with natively", default=None)
        self.parser.add_argument("--stories", help="Amount of top-level stories to binge natively", default=None)
        self.parser.add_argument("--repeat", help="Amount of times to repeat the whole process", default=None)
        self.parser.add_argument("--total-sessions", help="Total amount of sessions", default="-1")
        self.parser.add_argument("--working-hours", help="Working hours", default=None)
        self.parser.add_argument("--time-delta-session", help="Time delta between sessions", default=None)
        self.parser.add_argument(
            "--max-runtime-minutes", type=int, help="Maximum runtime in minutes before bot auto-exits", default=None
        )
        self.parser.add_argument("--restart-atx-agent", action="store_true", help="Restart atx agent")
        self.parser.add_argument("--allow-untested-ig-version", action="store_true", help="Allow untested IG version")
        self.parser.add_argument(
            "--blank-start",
            action="store_true",
            help="Wipe all learned navigation and telepathic memories on boot to start 100%% blank.",
        )

        self.parser.add_argument(
            "--goal",
            type=str,
            help="High-level autonomous goal for the bot (Tesla-style). Overrides config.yml goals.",
            default=None,
        )

        # Interaction settings
        self.parser.add_argument("--likes-count", help="Likes count", default="2-3")
        self.parser.add_argument("--likes-percentage", help="Likes percentage", default="100")
        self.parser.add_argument("--stories-count", help="Stories count", default="0")
        self.parser.add_argument("--stories-percentage", help="Stories percentage", default="0")

        # Total Limits (Legacy names preserved for SessionState compatibility)
        self.parser.add_argument("--total-likes-limit", help="Total likes limit", default="300")
        self.parser.add_argument("--total-follows-limit", help="Total follows limit", default="50")
        self.parser.add_argument("--total-unfollows-limit", help="Total unfollows limit", default="50")
        self.parser.add_argument("--total-comments-limit", help="Total comments limit", default="10")
        self.parser.add_argument("--total-pm-limit", help="Total pm limit", default="10")
        self.parser.add_argument("--total-watches-limit", help="Total watches limit", default="50")
        self.parser.add_argument(
            "--total-successful-interactions-limit", help="Total successful interactions limit", default="100"
        )
        self.parser.add_argument("--total-interactions-limit", help="Total interactions limit", default="1000")
        self.parser.add_argument("--total-scraped-limit", help="Total scraped limit", default="200")
        self.parser.add_argument("--total-crashes-limit", help="Total crashes limit", default="5")
        self.parser.add_argument("--speed-multiplier", help="Speed multiplier", default="1.0")

        # AI Model Configuration (centralized — no hardcoded model names anywhere)
        self.parser.add_argument(
            "--ai-model", "--ai-text-model", help="Primary LLM model (OpenRouter or Ollama)", default="qwen3.5:latest"
        )
        self.parser.add_argument(
            "--ai-model-url",
            "--ai-text-url",
            help="Primary LLM endpoint URL",
            default="http://localhost:11434/api/generate",
        )
        self.parser.add_argument(
            "--ai-telepathic-model", help="Text-based model for Telepathic Engine Fallbacks", default="qwen3.5:latest"
        )
        self.parser.add_argument(
            "--ai-telepathic-url", help="Telepathic model endpoint URL", default="http://localhost:11434/api/generate"
        )
        self.parser.add_argument(
            "--ai-fallback-model",
            "--ai-text-fallback-model",
            help="Fallback model when primary fails",
            default="qwen3.5:latest",
        )
        self.parser.add_argument(
            "--ai-fallback-url",
            "--ai-text-fallback-url",
            help="Fallback model endpoint URL",
            default="http://localhost:11434/api/generate",
        )
        self.parser.add_argument(
            "--ai-embedding-model", help="Embedding model for vector operations", default="nomic-embed-text"
        )
        self.parser.add_argument(
            "--ai-embedding-url", help="Embedding endpoint URL", default="http://localhost:11434/api/embeddings"
        )

        # Persona & Resonance (drives ALL content evaluation and interaction decisions)
        self.parser.add_argument(
            "--persona-interests", help="Comma-separated niche interests for content matching", default=""
        )
        self.parser.add_argument(
            "--ai-target-audience", help="Target audience used interchangeably with persona interests", default=""
        )
        self.parser.add_argument(
            "--target-audience", help="Target audience used interchangeably with persona interests", default=""
        )
        self.parser.add_argument(
            "--interact-percentage", help="Overall interaction probability percentage", default="80"
        )
        self.parser.add_argument("--comment-percentage", help="Comment probability percentage", default="0")
        self.parser.add_argument("--follow-percentage", help="Follow probability percentage", default="0")
        self.parser.add_argument(
            "--dry-run-comments",
            action="store_true",
            help="Generate AI comments but do not actually post them (debug/logging only)",
        )
        self.parser.add_argument("--search", help="Comma-separated keywords to search for", default="")
        self.parser.add_argument(
            "--scrape-profiles", action="store_true", help="Extract and store profile metadata in CRM"
        )
        self.parser.add_argument(
            "--profile-learning-percentage", help="Percentage of profiles to deeply scan before engaging", default="0"
        )
        self.parser.add_argument(
            "--visual-vibe-check-percentage",
            help="Percentage of profiles to visually evaluate via screenshot before engaging",
            default="0",
        )
        self.parser.add_argument(
            "--ignore-close-friends",
            action="store_true",
            help="Completely ignore posts, stories, and profiles of Close Friends (Enge Freunde)",
        )

        # Biomechanical Physics
        self.parser.add_argument(
            "--handedness",
            help="Dominant hand: 'right' or 'left'. Affects thumb arc direction and tap bias.",
            default="right",
        )

        # Phase 10: RAG Comment Learning & Extractor Settings
        self.parser.add_argument(
            "--ai-condenser-model", help="LLM used for condensing text/comments", default="qwen3.5:latest"
        )
        self.parser.add_argument(
            "--ai-condenser-url", help="URL for the condenser model", default="http://localhost:11434/api/generate"
        )
        self.parser.add_argument(
            "--ai-learn-comments", action="store_true", help="Extract and learn from comment sections"
        )
        self.parser.add_argument("--ai-learn-niche-posts", action="store_true", help="Learn from niche posts")
        self.parser.add_argument(
            "--ai-learn-own-profile", action="store_true", help="Learn from your own profile interactions"
        )
        self.parser.add_argument(
            "--ai-learn-only", action="store_true", help="Run the bot in a pure read-only learning mode"
        )
        self.parser.add_argument(
            "--ai-vibe", help="The specific vibe to extract from comments (e.g., friendly, controversial)", default=""
        )
        self.parser.add_argument(
            "--ai-blacklist-topics", help="Comma-separated topics heavily penalized or skipped", default=""
        )
        self.parser.add_argument(
            "--ai-quality-filter",
            action="store_true",
            help="Use AI to strictly filter the quality of posts and comments",
        )
        self.parser.add_argument(
            "--smart-unfollow",
            action="store_true",
            help="Enable agentic decision making for clearing the following list",
        )
        self.parser.add_argument(
            "--ai-vision-navigation",
            action="store_true",
            help="Capture and send base64 UI screenshots to the LLM for structural element finding",
        )
        self.parser.add_argument(
            "--ai-vision-context",
            action="store_true",
            help="Capture and send base64 post/DM screenshots to the LLM for contextual semantic generation",
        )

        # on first run, we must wait to proceed with loading
        if not self.first_run:
            self.parse_args()

    def parse_args(self):
        if self.module:
            if self.first_run:
                logger.debug("Arguments used:")
                if self.config:
                    logger.debug(f"Config used: {self.config}")
                if len(self.args) == 0:
                    self.parser.print_help()
                    exit(0)
        else:
            if self.first_run:
                logger.debug(f"Arguments used: {' '.join(sys.argv[1:])}")
                if self.config:
                    logger.debug(f"Config used: {self.config}")
                if len(sys.argv) <= 1 and not self.config:
                    self.parser.print_help()
                    exit(0)
        if self.config:
            cleaned_config = {}

            def flatten_dict(d, parent_key="", sep="_"):
                items = []
                for k, v in d.items():
                    # Special handling for 'plugins' key: we want 'like: count' to become 'like_count'
                    if k == "plugins" and not parent_key:
                        if isinstance(v, dict):
                            for pk, pv in v.items():
                                items.extend(flatten_dict(pv, pk, sep=sep).items())
                            continue

                    if isinstance(v, dict) and k not in ["username", "passwords"]:
                        # If we are inside a plugin, continue prefixing
                        next_prefix = f"{parent_key}{sep}{k}" if parent_key else ""
                        items.extend(flatten_dict(v, next_prefix, sep=sep).items())
                    else:
                        full_key = f"{parent_key}{sep}{k}" if parent_key else k
                        items.append((full_key, v))
                return dict(items)

            flat_config = flatten_dict(self.config)

            for k, v in flat_config.items():
                val = v
                if isinstance(v, dict):
                    val = "SPECIALIZED"

                cleaned_config[k.replace("-", "_")] = val
            self.parser.set_defaults(**cleaned_config)

        if self.module:
            arg_str = ""
            for k, v in self.args.items():
                new_key = k.replace("_", "-")
                new_key = f" --{new_key}"
                arg_str += f"{new_key} {v}"
            self.args, self.unknown_args = self.parser.parse_known_args(args=arg_str)
        else:
            self.args, self.unknown_args = self.parser.parse_known_args()

        self.device_id = self.args.device

        # Map actions
        if getattr(self.args, "feed", None):
            self.enabled.append("feed")
        if getattr(self.args, "explore", None):
            self.enabled.append("explore")

    def specialize(self, username):
        if self.config is None:
            return

        logger.debug(f"Specializing config for account: {username}")
        for key, value in self.config.items():
            if isinstance(value, dict) and username in value:
                resolved_value = value[username]
                arg_name = key.replace("-", "_")
                if hasattr(self.args, arg_name):
                    setattr(self.args, arg_name, resolved_value)
                    logger.info(
                        f"Applied override for {username}: {key} = {resolved_value}",
                        extra={"color": f"{Style.BRIGHT}{Fore.BLUE}"},
                    )

        # Handle the case where username itself is a list - we specialize it to the current target
        self.args.username = [username] if isinstance(self.args.username, list) else username

    def get_plugin_config(self, plugin_name: str) -> dict:
        """
        Retrieves configuration for a specific plugin.
        First checks the 'plugins' dict. If not found, falls back to flat config values
        using the plugin_name as a prefix for backward compatibility.
        """
        if self.config and "plugins" in self.config:
            plugin_dict = self.config["plugins"].get(plugin_name, {})
            if plugin_dict:
                return plugin_dict

        # Backward compatibility / flat config fallback
        # e.g., for "follow" plugin, check if "follow_percentage" exists
        fallback = {}
        if hasattr(self.args, f"{plugin_name}_percentage"):
            fallback["percentage"] = getattr(self.args, f"{plugin_name}_percentage")

        # specific hardcoded fallbacks
        if plugin_name == "close_friends_guard" and hasattr(self.args, "ignore_close_friends"):
            fallback["enabled"] = getattr(self.args, "ignore_close_friends")

        if plugin_name == "comment_interaction" and hasattr(self.args, "dry_run_comments"):
            fallback["dry_run"] = getattr(self.args, "dry_run_comments")

        return fallback


def get_time_last_save(file_path) -> str:
    try:
        absolute_file_path = os.path.abspath(file_path)
        timestamp = os.path.getmtime(absolute_file_path)
        last_save = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
        return f"{file_path} has been saved last time at {last_save}"
    except FileNotFoundError:
        return f"File {file_path} not found"
