"""Factor store for OpenClaw Trading.

This module provides the FactorStore class for managing factor purchases,
inventory, and unlock status. It integrates with the agent economy system
to handle payments and balance tracking.
"""

from datetime import datetime
from pathlib import Path
from typing import Any, Optional

from pydantic import BaseModel

from openclaw.core.economy import TradingEconomicTracker
from openclaw.factor.advanced import (
    MachineLearningFactor,
    MultiFactorCombination,
    SentimentMomentumFactor,
)
from openclaw.factor.base import Factor
from openclaw.factor.basic import (
    BollingerBandBreakoutFactor,
    MACDCrossoverFactor,
    MovingAverageCrossoverFactor,
    RSIOversoldFactor,
)
from openclaw.factor.types import FactorInventoryItem, PurchaseRecord
from openclaw.utils.logging import get_logger


class FactorStoreState(BaseModel):
    """Complete state of the factor store for persistence."""

    agent_id: str
    inventory: list[FactorInventoryItem]
    purchase_history: list[PurchaseRecord]
    balance_at_last_purchase: float


class PurchaseResult:
    """Result of a factor purchase attempt."""

    def __init__(
        self,
        success: bool,
        message: str,
        factor_id: Optional[str] = None,
        price: float = 0.0,
        new_balance: Optional[float] = None,
    ):
        self.success = success
        self.message = message
        self.factor_id = factor_id
        self.price = price
        self.new_balance = new_balance

    def __repr__(self) -> str:
        status = "SUCCESS" if self.success else "FAILED"
        return (
            f"PurchaseResult({status}: {self.message}, "
            f"price=${self.price:.2f})"
        )


class FactorStore:
    """Store for purchasing and managing trading factors.

    The FactorStore maintains a catalog of available factors, handles
    purchase transactions, and manages the agent's factor inventory.
    It integrates with TradingEconomicTracker for payment processing.

    Args:
        agent_id: Unique identifier for the agent
        tracker: Economic tracker for balance management
        auto_unlock_free: Automatically unlock free factors (default: True)
    """

    def __init__(
        self,
        agent_id: str,
        tracker: TradingEconomicTracker,
        auto_unlock_free: bool = True,
    ):
        self.agent_id = agent_id
        self.tracker = tracker
        self.auto_unlock_free = auto_unlock_free
        self.logger = get_logger(f"factor_store.{agent_id}")

        # Factor catalog - all available factors
        self._catalog: dict[str, Factor] = {}
        self._init_catalog()

        # Agent's inventory - owned factors
        self._inventory: dict[str, FactorInventoryItem] = {}

        # Purchase history
        self._purchase_history: list[PurchaseRecord] = []

        # Initialize free factors if enabled
        if auto_unlock_free:
            self._auto_unlock_free_factors()

    def _init_catalog(self) -> None:
        """Initialize the factor catalog with all available factors."""
        # Basic factors (free)
        basic_factors = [
            MovingAverageCrossoverFactor(),
            RSIOversoldFactor(),
            MACDCrossoverFactor(),
            BollingerBandBreakoutFactor(),
        ]

        # Advanced factors (paid)
        advanced_factors = [
            MachineLearningFactor(),
            SentimentMomentumFactor(),
            MultiFactorCombination(),
        ]

        # Register all factors
        for factor in basic_factors + advanced_factors:
            factor.initialize()
            self._catalog[factor.id] = factor

        self.logger.info(
            f"Catalog initialized with {len(self._catalog)} factors: "
            f"{len(basic_factors)} basic, {len(advanced_factors)} advanced"
        )

    def _auto_unlock_free_factors(self) -> None:
        """Automatically unlock all free basic factors."""
        for factor_id, factor in self._catalog.items():
            if factor.is_free:
                factor.unlock()
                self._inventory[factor_id] = FactorInventoryItem(
                    factor_id=factor_id,
                    factor_name=factor.metadata.name,
                    unlocked=True,
                )
                self.logger.debug(f"Auto-unlocked free factor: {factor.metadata.name}")

    @property
    def catalog(self) -> dict[str, Factor]:
        """Get the factor catalog."""
        return self._catalog.copy()

    @property
    def inventory(self) -> dict[str, FactorInventoryItem]:
        """Get the agent's factor inventory."""
        return self._inventory.copy()

    def list_available(self) -> list[dict[str, Any]]:
        """List all available factors with their purchase status.

        Returns:
            List of factor information dictionaries
        """
        result = []
        for factor_id, factor in self._catalog.items():
            owned = factor_id in self._inventory
            item = self._inventory.get(factor_id)

            result.append(
                {
                    "id": factor_id,
                    "name": factor.metadata.name,
                    "description": factor.metadata.description,
                    "type": factor.metadata.factor_type.value,
                    "category": factor.metadata.category.value,
                    "price": factor.metadata.price,
                    "owned": owned,
                    "unlocked": item.unlocked if item else False,
                    "usage_count": item.usage_count if item else 0,
                    "tags": factor.metadata.tags,
                }
            )

        # Sort by category then by price
        result.sort(key=lambda x: (x["category"], x["price"]))
        return result

    def list_owned(self) -> list[dict[str, Any]]:
        """List factors owned by the agent.

        Returns:
            List of owned factor information dictionaries
        """
        result = []
        for factor_id, item in self._inventory.items():
            factor = self._catalog.get(factor_id)
            if factor:
                result.append(
                    {
                        "id": factor_id,
                        "name": item.factor_name,
                        "category": factor.metadata.category.value,
                        "price": factor.metadata.price,
                        "unlocked": item.unlocked,
                        "usage_count": item.usage_count,
                        "last_used": item.last_used.isoformat()
                        if item.last_used
                        else None,
                        "purchased_at": item.purchased_at.isoformat(),
                    }
                )

        return result

    def get_factor(self, factor_id: str) -> Optional[Factor]:
        """Get a factor by ID if owned and unlocked.

        Args:
            factor_id: Factor identifier

        Returns:
            Factor instance if available, None otherwise
        """
        if factor_id not in self._inventory:
            self.logger.warning(f"Factor not owned: {factor_id}")
            return None

        item = self._inventory[factor_id]
        if not item.unlocked:
            self.logger.warning(f"Factor not unlocked: {factor_id}")
            return None

        factor = self._catalog.get(factor_id)
        if factor:
            item.mark_used()

        return factor

    def get_factor_info(self, factor_id: str) -> Optional[dict[str, Any]]:
        """Get detailed information about a factor.

        Args:
            factor_id: Factor identifier

        Returns:
            Factor information dictionary if found, None otherwise
        """
        factor = self._catalog.get(factor_id)
        if not factor:
            return None

        item = self._inventory.get(factor_id)

        return {
            "id": factor_id,
            "name": factor.metadata.name,
            "description": factor.metadata.description,
            "type": factor.metadata.factor_type.value,
            "category": factor.metadata.category.value,
            "price": factor.metadata.price,
            "author": factor.metadata.author,
            "version": factor.metadata.version,
            "tags": factor.metadata.tags,
            "min_data_points": factor.metadata.min_data_points,
            "parameters": factor.metadata.parameters,
            "owned": factor_id in self._inventory,
            "unlocked": item.unlocked if item else False,
            "usage_count": item.usage_count if item else 0,
        }

    def purchase(self, factor_id: str) -> PurchaseResult:
        """Purchase a factor.

        Args:
            factor_id: Factor identifier to purchase

        Returns:
            PurchaseResult with success status and details
        """
        # Check if factor exists
        factor = self._catalog.get(factor_id)
        if not factor:
            return PurchaseResult(
                success=False,
                message=f"Factor not found: {factor_id}",
                factor_id=factor_id,
            )

        # Check if already owned
        if factor_id in self._inventory:
            return PurchaseResult(
                success=False,
                message=f"Factor already owned: {factor.metadata.name}",
                factor_id=factor_id,
                price=0.0,
            )

        price = factor.metadata.price

        # Check if free
        if price == 0:
            factor.unlock()
            self._inventory[factor_id] = FactorInventoryItem(
                factor_id=factor_id,
                factor_name=factor.metadata.name,
                unlocked=True,
            )

            record = PurchaseRecord(
                factor_id=factor_id,
                factor_name=factor.metadata.name,
                price=0.0,
                agent_id=self.agent_id,
            )
            self._purchase_history.append(record)

            self.logger.info(f"Free factor acquired: {factor.metadata.name}")
            return PurchaseResult(
                success=True,
                message=f"Free factor acquired: {factor.metadata.name}",
                factor_id=factor_id,
                price=0.0,
                new_balance=self.tracker.balance,
            )

        # Check balance
        if self.tracker.balance < price:
            return PurchaseResult(
                success=False,
                message=f"Insufficient balance: need ${price:.2f}, have ${self.tracker.balance:.2f}",
                factor_id=factor_id,
                price=price,
            )

        # Deduct balance using tracker (public deduct for consistency)
        if not self.tracker.deduct(price, f"Purchase factor: {factor.metadata.name}"):
            return PurchaseResult(
                success=False,
                message=f"Insufficient balance: need ${price:.2f}, have ${self.tracker.balance:.2f}",
                factor_id=factor_id,
                price=price,
            )

        # Unlock factor
        factor.unlock()
        self._inventory[factor_id] = FactorInventoryItem(
            factor_id=factor_id,
            factor_name=factor.metadata.name,
            unlocked=True,
        )

        # Record purchase
        record = PurchaseRecord(
            factor_id=factor_id,
            factor_name=factor.metadata.name,
            price=price,
            agent_id=self.agent_id,
        )
        self._purchase_history.append(record)

        self.logger.info(
            f"Factor purchased: {factor.metadata.name} for ${price:.2f}. "
            f"New balance: ${self.tracker.balance:.2f}"
        )

        return PurchaseResult(
            success=True,
            message=f"Factor purchased: {factor.metadata.name}",
            factor_id=factor_id,
            price=price,
            new_balance=self.tracker.balance,
        )

    def get_purchase_history(self) -> list[PurchaseRecord]:
        """Get purchase history.

        Returns:
            List of purchase records
        """
        return self._purchase_history.copy()

    def get_inventory_value(self) -> float:
        """Calculate total value of owned factors.

        Returns:
            Sum of all owned factor prices
        """
        total = 0.0
        for factor_id in self._inventory:
            factor = self._catalog.get(factor_id)
            if factor:
                total += factor.metadata.price
        return total

    def get_store_summary(self) -> dict[str, Any]:
        """Get summary of store state.

        Returns:
            Summary dictionary with counts and values
        """
        basic_owned = sum(
            1
            for fid in self._inventory
            if self._catalog.get(fid)
            and self._catalog[fid].metadata.category.value == "basic"
        )
        advanced_owned = sum(
            1
            for fid in self._inventory
            if self._catalog.get(fid)
            and self._catalog[fid].metadata.category.value == "advanced"
        )
        premium_owned = sum(
            1
            for fid in self._inventory
            if self._catalog.get(fid)
            and self._catalog[fid].metadata.category.value == "premium"
        )

        total_basic = sum(
            1
            for f in self._catalog.values()
            if f.metadata.category.value == "basic"
        )
        total_advanced = sum(
            1
            for f in self._catalog.values()
            if f.metadata.category.value == "advanced"
        )
        total_premium = sum(
            1
            for f in self._catalog.values()
            if f.metadata.category.value == "premium"
        )

        return {
            "agent_id": self.agent_id,
            "current_balance": self.tracker.balance,
            "inventory_value": self.get_inventory_value(),
            "total_purchases": len(self._purchase_history),
            "total_spent": sum(r.price for r in self._purchase_history),
            "factors_owned": {
                "basic": f"{basic_owned}/{total_basic}",
                "advanced": f"{advanced_owned}/{total_advanced}",
                "premium": f"{premium_owned}/{total_premium}",
            },
        }

    def save_to_file(self, filepath: str | Path) -> None:
        """Save store state to file.

        Args:
            filepath: Path to save the state file
        """
        path = Path(filepath)
        path.parent.mkdir(parents=True, exist_ok=True)

        state = FactorStoreState(
            agent_id=self.agent_id,
            inventory=list(self._inventory.values()),
            purchase_history=self._purchase_history,
            balance_at_last_purchase=self.tracker.balance,
        )

        with open(path, "w", encoding="utf-8") as f:
            f.write(state.model_dump_json(indent=2))

        self.logger.info(f"Factor store state saved to {filepath}")

    @classmethod
    def load_from_file(
        cls,
        filepath: str | Path,
        tracker: TradingEconomicTracker,
    ) -> "FactorStore":
        """Load store from file.

        Args:
            filepath: Path to the state file
            tracker: Economic tracker for the agent

        Returns:
            FactorStore restored from file
        """
        path = Path(filepath)

        if not path.exists():
            raise FileNotFoundError(f"State file not found: {filepath}")

        state_data = path.read_text(encoding="utf-8")
        state = FactorStoreState.model_validate_json(state_data)

        # Create new store
        store = cls(agent_id=state.agent_id, tracker=tracker, auto_unlock_free=False)

        # Restore inventory
        for item in state.inventory:
            store._inventory[item.factor_id] = item
            # Unlock corresponding factor
            if item.factor_id in store._catalog:
                store._catalog[item.factor_id].unlock()

        # Restore purchase history
        store._purchase_history = state.purchase_history

        store.logger.info(f"Factor store state loaded from {filepath}")
        return store

    def __repr__(self) -> str:
        """String representation of the store."""
        return (
            f"FactorStore("
            f"agent_id='{self.agent_id}', "
            f"factors_owned={len(self._inventory)}, "
            f"catalog_size={len(self._catalog)}, "
            f"balance=${self.tracker.balance:.2f}"
            f")"
        )
