"""
Bot factory for creating and validating Telegram bots
"""
import os
import re
import signal
from typing import Optional, List
from telegram import Update, BotCommand as TelegramBotCommand
from telegram.ext import (
    Application,
    CommandHandler as TelegramCommandHandler,
    MessageHandler as TelegramMessageHandler,
    ContextTypes,
    filters
)
from .types import BotDefinition, ValidationResult, BotCommand, BotMessageHandler
from .logger import Logger
from .firebase import FirebaseClient


class BotInstance:
    """Bot instance with lifecycle methods"""

    def __init__(
        self,
        app: Application,
        definition: BotDefinition,
        logger: Logger
    ):
        """
        Initialize bot instance

        Args:
            app: Telegram application instance
            definition: Bot definition
            logger: Logger instance
        """
        self.app = app
        self.definition = definition
        self.logger = logger
        self.bot_name = definition["config"]["name"]
        self._running = False

    async def start(self) -> None:
        """Start the bot"""
        if self._running:
            self.logger.warn("Bot already running")
            return

        # Call onStart hook if defined
        on_start = self.definition.get("on_start")
        if on_start:
            await on_start()

        # Start the application
        await self.app.initialize()
        await self.app.start()

        self.logger.bot_start()
        self._running = True

    async def stop(self) -> None:
        """Stop the bot"""
        if not self._running:
            return

        self.logger.info("Stopping bot...")

        # Call onStop hook if defined
        on_stop = self.definition.get("on_stop")
        if on_stop:
            await on_stop()

        # Stop the application
        await self.app.stop()
        await self.app.shutdown()

        self._running = False
        self.logger.info("Bot stopped")

    def is_running(self) -> bool:
        """Check if bot is running"""
        return self._running


class BotFactory:
    """Factory for creating and validating bots"""

    @staticmethod
    def validate(definition: BotDefinition) -> ValidationResult:
        """
        Validate bot definition

        Args:
            definition: Bot definition to validate

        Returns:
            ValidationResult with valid flag and error list
        """
        errors: List[str] = []

        # Check config
        if "config" not in definition:
            errors.append("Missing required field: config")
        else:
            config = definition["config"]
            if "name" not in config:
                errors.append("Missing required field: config.name")

        # Check commands
        if "commands" not in definition:
            errors.append("Missing required field: commands")
        elif not isinstance(definition["commands"], list):
            errors.append("Field 'commands' must be an array")
        elif len(definition["commands"]) == 0:
            errors.append("At least one command is required")
        else:
            # Validate each command
            for i, cmd in enumerate(definition["commands"]):
                if not isinstance(cmd, dict):
                    errors.append(f"Command at index {i} must be an object")
                    continue

                if "command" not in cmd:
                    errors.append(f"Command at index {i} missing required field: command")
                if "description" not in cmd:
                    errors.append(f"Command at index {i} missing required field: description")
                if "handler" not in cmd:
                    errors.append(f"Command at index {i} missing required field: handler")
                elif not callable(cmd["handler"]):
                    errors.append(f"Command at index {i}: handler must be callable")

        # Check message handlers (optional)
        if "message_handlers" in definition:
            message_handlers = definition["message_handlers"]
            if not isinstance(message_handlers, list):
                errors.append("Field 'message_handlers' must be an array")
            else:
                for i, handler in enumerate(message_handlers):
                    if not isinstance(handler, dict):
                        errors.append(f"Message handler at index {i} must be an object")
                        continue

                    if "handler" not in handler:
                        errors.append(f"Message handler at index {i} missing required field: handler")
                    elif not callable(handler["handler"]):
                        errors.append(f"Message handler at index {i}: handler must be callable")

        # Check lifecycle hooks (optional)
        if "on_start" in definition and not callable(definition["on_start"]):
            errors.append("Field 'on_start' must be callable")
        if "on_stop" in definition and not callable(definition["on_stop"]):
            errors.append("Field 'on_stop' must be callable")

        return {
            "valid": len(errors) == 0,
            "errors": errors
        }

    @staticmethod
    def create(definition: BotDefinition) -> BotInstance:
        """
        Create bot instance from definition

        Args:
            definition: Bot definition

        Returns:
            BotInstance

        Raises:
            ValueError: If definition is invalid
            RuntimeError: If bot token not provided
        """
        # Validate definition
        validation = BotFactory.validate(definition)
        if not validation["valid"]:
            error_msg = "\n".join(validation["errors"])
            raise ValueError(f"Invalid bot definition:\n{error_msg}")

        # Get bot config
        config = definition["config"]
        bot_name = config["name"]

        # Get bot token
        token = config.get("token") or os.getenv("BOT_TOKEN")
        if not token:
            raise RuntimeError(
                f"Bot token not provided for '{bot_name}'. "
                "Set BOT_TOKEN environment variable or provide in config.token"
            )

        # Create logger
        logger = Logger.for_bot(bot_name)

        # Create application
        app = Application.builder().token(token).build()

        # Add Firebase to context if initialized
        if FirebaseClient.is_initialized():
            app.bot_data["firebase"] = FirebaseClient.get_instance()

        # Register commands
        telegram_commands: List[TelegramBotCommand] = []
        for cmd in definition["commands"]:
            command_name = cmd["command"]
            description = cmd["description"]
            handler = cmd["handler"]

            # Wrap handler with logging and error handling
            async def wrapped_handler(
                update: Update,
                context: ContextTypes.DEFAULT_TYPE,
                _cmd=command_name,
                _handler=handler
            ):
                try:
                    user_id = update.effective_user.id if update.effective_user else None
                    logger.command(_cmd, user_id)
                    await _handler(update, context)
                except Exception as e:
                    logger.error(f"Error in command /{_cmd}", e)
                    if update.effective_message:
                        await update.effective_message.reply_text(
                            "Sorry, an error occurred while processing your command."
                        )

            # Register command handler
            app.add_handler(TelegramCommandHandler(command_name, wrapped_handler))

            # Add to Telegram command list (for /setMyCommands)
            telegram_commands.append(TelegramBotCommand(command_name, description))

        # Set bot commands
        async def post_init(application: Application) -> None:
            await application.bot.set_my_commands(telegram_commands)

        app.post_init = post_init

        # Register message handlers
        message_handlers = definition.get("message_handlers", [])
        for msg_handler in message_handlers:
            handler_func = msg_handler["handler"]
            pattern = msg_handler.get("pattern")

            # Create filter
            msg_filter = filters.TEXT & ~filters.COMMAND

            if pattern:
                if isinstance(pattern, str):
                    # String pattern - exact match
                    msg_filter = msg_filter & filters.Regex(f"^{re.escape(pattern)}$")
                else:
                    # Regex pattern
                    msg_filter = msg_filter & filters.Regex(pattern)

            # Wrap handler with logging and error handling
            async def wrapped_msg_handler(
                update: Update,
                context: ContextTypes.DEFAULT_TYPE,
                _handler=handler_func
            ):
                try:
                    if update.message and update.message.text:
                        user_id = update.effective_user.id if update.effective_user else None
                        logger.message(update.message.text, user_id)
                    await _handler(update, context)
                except Exception as e:
                    logger.error("Error in message handler", e)
                    if update.effective_message:
                        await update.effective_message.reply_text(
                            "Sorry, an error occurred while processing your message."
                        )

            # Register message handler
            app.add_handler(TelegramMessageHandler(msg_filter, wrapped_msg_handler))

        # Setup graceful shutdown
        async def shutdown_handler(sig, frame):
            logger.info(f"Received signal {sig}, shutting down...")

        for sig in (signal.SIGINT, signal.SIGTERM):
            signal.signal(sig, lambda s, f: None)  # Prevent immediate exit

        # Create and return bot instance
        return BotInstance(app, definition, logger)
