from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from typing import List, Dict, Any
from datetime import datetime
import json

from app.modules.terminal.auto.models import TradingInstance, InstanceStatus, ScriptType, BotPreset, TelegramBot
from app.modules.terminal.auto.schema import DeployConfig, InstanceResponse
from app.modules.terminal.auto.crypto import CryptoService
from app.core.exceptions import BadRequestError, NotFoundError
from app.tasks.trading_tasks import start_trading_instance
from app.config import get_settings


class TerminalService:
    """Terminal trading service"""
    
    def __init__(self, db: AsyncSession, user_id: int, redis):
        self.db = db
        self.user_id = user_id
        self.redis = redis
        settings = get_settings()
        self.crypto = CryptoService(master_key=settings.SECRET_KEY)
    
    async def store_session_credentials(self, platform: str = "kalshi", **kwargs) -> None:
        """Store encrypted credentials in Redis session (user-configured TTL)"""
        session = {"platform": platform, "timestamp": datetime.utcnow().isoformat()}

        if platform == "kalshi":
            session["api_key"] = self.crypto.encrypt(kwargs["api_key"])
            session["rsa_key"] = self.crypto.encrypt(kwargs["rsa_key"])
        elif platform == "turbine":
            session["turbine_private_key"] = self.crypto.encrypt(kwargs["turbine_private_key"])
        elif platform == "polymarket":
            session["poly_private_key"] = self.crypto.encrypt(kwargs["poly_private_key"])

        # Use user-configured TTL (default 72h, clamped 1-96h)
        ttl_hours = await self._get_user_session_ttl()
        ttl_seconds = ttl_hours * 3600

        await self.redis.set(
            f"user:{self.user_id}:credentials",
            json.dumps(session),
            ex=ttl_seconds
        )

    async def _get_user_session_ttl(self) -> int:
        """Get user's configured session TTL in hours."""
        cached = await self.redis.get(f"user:{self.user_id}")
        if cached:
            data = json.loads(cached)
            return max(1, min(96, data.get("session_ttl_hours", 72)))
        # Fallback: query DB
        from app.modules.user.models import User
        from sqlalchemy import select
        result = await self.db.execute(select(User.session_ttl_hours).where(User.id == self.user_id))
        ttl = result.scalar_one_or_none()
        return max(1, min(96, ttl or 72))

    async def get_session_credentials(self) -> Dict[str, str]:
        """Get decrypted credentials from Redis session"""
        session_key = f"user:{self.user_id}:credentials"
        session_data = await self.redis.get(session_key)

        if not session_data:
            raise BadRequestError("Session expired. Please re-enter your credentials.")

        data = json.loads(session_data)
        platform = data.get("platform", "kalshi")

        if platform == "kalshi":
            return {
                "platform": "kalshi",
                "api_key": self.crypto.decrypt(data["api_key"]),
                "rsa_key": self.crypto.decrypt(data["rsa_key"])
            }
        elif platform == "turbine":
            return {
                "platform": "turbine",
                "turbine_private_key": self.crypto.decrypt(data["turbine_private_key"])
            }
        elif platform == "polymarket":
            return {
                "platform": "polymarket",
                "poly_private_key": self.crypto.decrypt(data["poly_private_key"])
            }
    
    async def clear_session_credentials(self) -> None:
        """Delete credentials from Redis (on logout)"""
        await self.redis.delete(f"user:{self.user_id}:credentials")
    
    async def deploy_instance(self, config: DeployConfig) -> TradingInstance:
        """Deploy a new trading instance"""
        platform = config.platform

        if platform == "kalshi":
            rsa_private_key = config.rsa_key_path
            if "BEGIN PRIVATE KEY" not in rsa_private_key and "BEGIN RSA PRIVATE KEY" not in rsa_private_key:
                raise BadRequestError("Invalid RSA private key format")
            await self.store_session_credentials(
                platform="kalshi",
                api_key=config.kalshi_api_key,
                rsa_key=rsa_private_key,
            )
        elif platform == "turbine":
            if not config.turbine_private_key:
                raise BadRequestError("Turbine wallet private key is required")
            await self.store_session_credentials(
                platform="turbine",
                turbine_private_key=config.turbine_private_key,
            )
        elif platform == "polymarket":
            if not config.poly_private_key:
                raise BadRequestError("Polymarket wallet private key is required")
            await self.store_session_credentials(
                platform="polymarket",
                poly_private_key=config.poly_private_key,
            )

        # Determine script type
        if platform == "turbine":
            script = ScriptType.AUTO1  # Turbine only supports single market
        elif platform == "polymarket":
            # interval = auto1, event (2-market) = auto2
            if config.poly_market_type == "interval":
                script = ScriptType.AUTO1
            else:
                script = ScriptType.AUTO2
        elif config.mode == "hotkeys":
            script = ScriptType.KEYS1 if config.num_markets == 1 else ScriptType.KEYS2
        else:
            script = ScriptType.AUTO1 if config.num_markets == 1 else ScriptType.AUTO2

        # Build configuration
        instance_config = {
            "platform": platform,
            "both_side": config.both_side,
            "market_priority": config.market_priority,
            "side_priority": config.side_priority,
            "min_spread": config.min_spread,
            "max_spread": config.max_spread,
            "m1_bounds": config.m1_bounds,
            "m2_bounds": config.m2_bounds,
            "position_increment": config.position_increment,
            "max_position": config.max_position,
            "trade_strategy": config.trade_strategy,
            "join_only": config.join_only,
            "grid_mode": config.grid_mode,
            "grid_levels": config.grid_levels,
            "price_feed": config.price_feed,
            "rolling_avg_window": config.rolling_avg_window,
            "rolling_avg_spread": config.rolling_avg_spread,
            "contract_increment": config.contract_increment,
            "interval_mode": config.interval_mode,
            "interval_repeating": config.interval_repeating,
        }
        if platform == "turbine":
            instance_config["turbine_asset"] = config.turbine_asset
        if platform == "polymarket":
            instance_config["poly_market_type"] = config.poly_market_type
            instance_config["poly_asset"] = config.poly_asset
            instance_config["poly_interval"] = config.poly_interval
        
        # Validate telegram bot if specified
        telegram_bot_id = None
        if config.telegram_bot_id:
            bot = await self._get_user_telegram_bot(config.telegram_bot_id)
            if bot.active_instance_id is not None:
                raise BadRequestError("This Telegram bot is already in use by another instance")
            telegram_bot_id = bot.id

        # Create instance
        instance = TradingInstance(
            user_id=self.user_id,
            script=script,
            markets={"markets": config.markets},
            config=instance_config,
            status=InstanceStatus.PENDING,
            start_time=datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
            telegram_bot_id=telegram_bot_id,
        )

        self.db.add(instance)
        await self.db.commit()
        await self.db.refresh(instance)

        # Mark bot as active
        if telegram_bot_id:
            bot.active_instance_id = instance.id
            await self.db.commit()
        
        # Start Celery task
        task = start_trading_instance.delay(
            instance_id=instance.id,
            user_id=self.user_id,
            script_type=script.value,
            markets=config.markets,
            config=instance_config
        )
        
        instance.celery_task_id = task.id
        instance.status = InstanceStatus.RUNNING
        await self.db.commit()

        # Save to recent events
        await self._update_recent_events(config.markets, config.num_markets)

        return instance

    async def _update_recent_events(self, markets: list, num_markets: int) -> None:
        """Update user's recent_events after a successful deploy."""
        from app.modules.user.models import User

        result = await self.db.execute(
            select(User).where(User.id == self.user_id)
        )
        user = result.scalar_one_or_none()
        if not user:
            return

        recent = user.recent_events or []

        # Dedup key: sorted markets list
        new_key = sorted(markets)
        recent = [
            entry for entry in recent
            if sorted(entry["markets"]) != new_key
        ]

        # Prepend new entry
        new_entry = {
            "markets": markets,
            "num_markets": num_markets,
            "deployed_at": datetime.utcnow().isoformat() + "Z"
        }
        recent.insert(0, new_entry)

        # Trim to max 5
        user.recent_events = recent[:5]
        await self.db.commit()

        # Invalidate Redis user cache
        await self.redis.delete(f"user:{self.user_id}")

    async def get_user_instances(self) -> List[TradingInstance]:
        """Get all instances for user (exclude DEAD)"""
        result = await self.db.execute(
            select(TradingInstance)
            .where(
                TradingInstance.user_id == self.user_id,
                TradingInstance.status != InstanceStatus.DEAD
            )
            .order_by(TradingInstance.created_at.desc())
        )
        return list(result.scalars().all())
    
    async def get_instance(self, instance_id: int) -> TradingInstance:
        """Get specific instance"""
        result = await self.db.execute(
            select(TradingInstance).where(
                TradingInstance.id == instance_id,
                TradingInstance.user_id == self.user_id
            )
        )
        instance = result.scalar_one_or_none()
        if not instance:
            raise NotFoundError("Trading instance not found")
        return instance
    
    async def cancel_orders_instance(self, instance_id: int) -> dict:
        """Cancel all resting orders on instance (z key equivalent)"""
        instance = await self.get_instance(instance_id)

        command_key = f"trading:instance:{instance_id}:command"
        await self.redis.rpush(command_key, json.dumps({"action": "cancel_orders"}))

        return {"instance_id": instance_id, "action": "cancel_orders"}

    async def toggle_pause_instance(self, instance_id: int) -> dict:
        """Toggle pause/resume for instance"""
        instance = await self.get_instance(instance_id)

        command_key = f"trading:instance:{instance_id}:command"
        await self.redis.rpush(command_key, json.dumps({"action": "toggle_pause"}))

        return {"instance_id": instance_id, "action": "toggle_pause"}
    
    async def single_fire_instance(self, instance_id: int) -> dict:
        """Execute single fire on instance"""
        instance = await self.get_instance(instance_id)
        
        command_key = f"trading:instance:{instance_id}:command"
        await self.redis.rpush(command_key, json.dumps({"action": "single_fire"}))
        
        return {"instance_id": instance_id, "action": "single_fire"}
    
    async def toggle_fair_value_instance(self, instance_id: int) -> dict:
        """Toggle fair value tracking"""
        instance = await self.get_instance(instance_id)
        
        command_key = f"trading:instance:{instance_id}:command"
        await self.redis.rpush(command_key, json.dumps({"action": "toggle_fair_value"}))
        
        return {"instance_id": instance_id, "action": "toggle_fair_value"}
    
    async def stop_instance(self, instance_id: int) -> TradingInstance:
        """Stop instance after current cycle completes"""
        instance = await self.get_instance(instance_id)
        
        if instance.status in [InstanceStatus.STOPPED, InstanceStatus.ERROR]:
            raise BadRequestError("Instance is already stopped")
        
        # Send stop signal via Redis - trading script will handle completion
        await self.redis.rpush(
            f"trading:instance:{instance_id}:command",
            json.dumps({"action": "stop"})
        )
        
        # Don't change status yet - let trading script handle it after cycle completes
        return instance
    
    async def end_instance(self, instance_id: int) -> TradingInstance:
        """Remove an instance (set to DEAD status)."""
        instance = await self.get_instance(instance_id)
        instance.status = InstanceStatus.DEAD
        # Free associated telegram bot
        await self._free_telegram_bot(instance_id)
        await self.db.commit()
        return instance
    
    async def get_instance_status(self, instance_id: int) -> Dict[str, Any]:
        """Get real-time instance status from Redis"""
        instance = await self.get_instance(instance_id)
        
        # Get cached data from Redis
        status_data = await self.redis.get(f"trading:instance:{instance_id}:status")
        
        if status_data:
            status_data = json.loads(status_data)
        else:
            status_data = {
                "position": instance.position,
                "pnl": instance.pnl,
                "orderbook": instance.orderbook_data or {},
                "current_increment": instance.current_increment or {}
            }
        
        return {
            "id": instance.id,
            "status": instance.status.value,
            **status_data
        }
    
    def format_instance_response(self, instance: TradingInstance) -> InstanceResponse:
        """Format instance for API response"""
        # Determine trade mode
        if instance.config.get("grid_mode"):
            trade_mode = "Grid"
        else:
            trade_mode = "Join"
        
        # Format P&L
        pnl_value = instance.pnl
        pnl_str = f"+${pnl_value:.2f}" if pnl_value >= 0 else f"-${abs(pnl_value):.2f}"
        
        return InstanceResponse(
            id=instance.id,
            script=instance.script.value,
            markets=instance.markets.get("markets", []),
            status=instance.status.value,
            start_time=instance.start_time,
            position=instance.position,
            pnl=pnl_str,
            config=instance.config,
            trade_mode=trade_mode,
            orderbook=instance.orderbook_data,
            celery_task_id=instance.celery_task_id,
            current_increment=instance.current_increment,
            telegram_bot_id=instance.telegram_bot_id,
        )
    
    async def toggle_jump(self, instance_id: int, market_index: int) -> dict:
        """Toggle jump mode for a market"""
        instance = await self.get_instance(instance_id)

        # Send command via Redis
        command_key = f"trading:instance:{instance_id}:command"
        await self.redis.rpush(command_key, json.dumps({
            "action": "toggle_jump",
            "market_index": market_index
        }))

        # Get current state
        state_key = f"trading:instance:{instance_id}:state"
        state = await self.redis.get(state_key)

        if state:
            state_data = json.loads(state)
            jump_active = state_data.get("jump_active", {})
            market_key = f"market_{market_index}"
            is_active = jump_active.get(market_key, False)
            return {
                "market_index": market_index,
                "jump_active": not is_active
            }

        return {"market_index": market_index, "jump_active": True}

    async def force_stop_instance(self, instance_id: int) -> TradingInstance:
        """Force stop instance (cancel all orders immediately)"""
        instance = await self.get_instance(instance_id)
        
        if instance.status in [InstanceStatus.STOPPED, InstanceStatus.ERROR]:
            raise BadRequestError("Instance is already stopped")
        
        # Send force stop command
        await self.redis.rpush(
            f"trading:instance:{instance_id}:command",
            json.dumps({"action": "force_stop"})
        )
        
        # Don't change status yet - let trading script handle it
        return instance

    # ── Presets ─────────────────────────────────────────────

    async def get_presets(self) -> List[BotPreset]:
        """Get all syndicate presets (shared across all users)"""
        result = await self.db.execute(
            select(BotPreset).order_by(BotPreset.updated_at.desc())
        )
        return list(result.scalars().all())

    async def get_preset(self, preset_id: int) -> BotPreset:
        """Get specific preset (any user can view)"""
        result = await self.db.execute(
            select(BotPreset).where(BotPreset.id == preset_id)
        )
        preset = result.scalar_one_or_none()
        if not preset:
            raise NotFoundError("Preset not found")
        return preset

    async def create_preset(self, name: str, strategy_config: dict, username: str = "unknown") -> BotPreset:
        """Create a new syndicate preset"""
        preset = BotPreset(
            user_id=self.user_id,
            created_by_username=username,
            name=name,
            strategy_config=strategy_config,
        )
        self.db.add(preset)
        await self.db.commit()
        await self.db.refresh(preset)
        return preset

    async def update_preset(self, preset_id: int, name: str = None, strategy_config: dict = None) -> BotPreset:
        """Update a preset (only creator can update)"""
        preset = await self.get_preset(preset_id)
        if preset.user_id != self.user_id:
            raise BadRequestError("Only the creator can update this preset")
        if name is not None:
            preset.name = name
        if strategy_config is not None:
            preset.strategy_config = strategy_config
        await self.db.commit()
        await self.db.refresh(preset)
        return preset

    async def delete_preset(self, preset_id: int, is_admin: bool = False) -> None:
        """Delete a preset (creator or admin only)"""
        preset = await self.get_preset(preset_id)
        if preset.user_id != self.user_id and not is_admin:
            raise BadRequestError("Only the creator or an admin can delete this preset")
        await self.db.delete(preset)
        await self.db.commit()

    # ── Telegram Bots ──────────────────────────────────────────

    async def get_telegram_bots(self) -> List[TelegramBot]:
        """Get all telegram bots for user"""
        result = await self.db.execute(
            select(TelegramBot)
            .where(TelegramBot.user_id == self.user_id)
            .order_by(TelegramBot.created_at.desc())
        )
        return list(result.scalars().all())

    async def _get_user_telegram_bot(self, bot_id: int) -> TelegramBot:
        """Get a specific telegram bot owned by user"""
        result = await self.db.execute(
            select(TelegramBot).where(
                TelegramBot.id == bot_id,
                TelegramBot.user_id == self.user_id,
            )
        )
        bot = result.scalar_one_or_none()
        if not bot:
            raise NotFoundError("Telegram bot not found")
        return bot

    async def create_telegram_bot(self, name: str, bot_token: str, chat_id: str) -> TelegramBot:
        """Create a new telegram bot for user"""
        bot = TelegramBot(
            user_id=self.user_id,
            name=name,
            bot_token=bot_token,
            chat_id=chat_id,
        )
        self.db.add(bot)
        await self.db.commit()
        await self.db.refresh(bot)
        return bot

    async def update_telegram_bot(self, bot_id: int, name: str = None, bot_token: str = None, chat_id: str = None) -> TelegramBot:
        """Update a telegram bot"""
        bot = await self._get_user_telegram_bot(bot_id)
        if name is not None:
            bot.name = name
        if bot_token is not None:
            bot.bot_token = bot_token
        if chat_id is not None:
            bot.chat_id = chat_id
        await self.db.commit()
        await self.db.refresh(bot)
        return bot

    async def delete_telegram_bot(self, bot_id: int) -> None:
        """Delete a telegram bot (must not be active)"""
        bot = await self._get_user_telegram_bot(bot_id)
        if bot.active_instance_id is not None:
            raise BadRequestError("Cannot delete a bot that is currently in use by an instance")
        await self.db.delete(bot)
        await self.db.commit()

    async def _free_telegram_bot(self, instance_id: int) -> None:
        """Free any telegram bot associated with an instance"""
        result = await self.db.execute(
            select(TelegramBot).where(TelegramBot.active_instance_id == instance_id)
        )
        bot = result.scalar_one_or_none()
        if bot:
            bot.active_instance_id = None