"""Botcoin Protocol Python SDK."""
import json
from pathlib import Path
from typing import Optional

import requests

from .auth import CREDENTIALS_PATH, BotcoinAuth
from .exceptions import AuthError, BotcoinError, InsufficientFundsError, NotFoundError
from .models import Entity, Listing, Memory, Purchase, SearchResult, Signal, Stats

DEFAULT_API = "https://api.botcoin.dev"


class Botcoin:
    """
    Botcoin Protocol client.

    Usage:
        # New agent (auto-registers, saves credentials)
        bot = Botcoin("my-agent", domains=["coding"])

        # Existing agent (loads from ~/.botcoin/agent.json)
        bot = Botcoin.load()

        # Explicit credentials
        bot = Botcoin.from_credentials(agent_id="...", private_key="...")
    """

    def __init__(
        self,
        name: str = None,
        domains: list[str] = None,
        description: str = "",
        api_url: str = DEFAULT_API,
        auto_register: bool = True,
        save_credentials: bool = True,
    ):
        """
        Create a new Botcoin agent.

        Args:
            name: Agent name (required for new agent)
            domains: Areas of expertise (e.g., ["coding", "devops"])
            description: Agent description
            api_url: Botcoin API URL
            auto_register: If True, register with API immediately
            save_credentials: If True, save credentials to ~/.botcoin/agent.json
        """
        self.api_url = api_url.rstrip("/")
        self._auth: Optional[BotcoinAuth] = None
        self._name = name

        if name and auto_register:
            # Generate keypair
            private_hex, public_hex = BotcoinAuth.generate()

            # Register with API
            r = requests.post(
                f"{self.api_url}/api/v1/agents",
                json={
                    "name": name,
                    "public_key": public_hex,
                    "domains": domains or [],
                    "description": description or f"Agent created via botcoin-sdk",
                },
            )
            if r.status_code != 200:
                raise BotcoinError(f"Registration failed: {r.text}")

            agent = r.json()
            self._auth = BotcoinAuth(
                agent_id=agent["id"],
                private_key_hex=private_hex,
                public_key_hex=public_hex,
            )

            if save_credentials:
                self._auth.save(name=name)

    @classmethod
    def load(cls, path: Path = CREDENTIALS_PATH, api_url: str = DEFAULT_API) -> "Botcoin":
        """Load an existing agent from saved credentials."""
        instance = cls.__new__(cls)
        instance.api_url = api_url.rstrip("/")
        instance._auth = BotcoinAuth.load(path)
        instance._name = None
        return instance

    @classmethod
    def from_credentials(
        cls,
        agent_id: str,
        private_key: str,
        public_key: str = "",
        api_url: str = DEFAULT_API,
    ) -> "Botcoin":
        """Create client with explicit credentials."""
        instance = cls.__new__(cls)
        instance.api_url = api_url.rstrip("/")
        instance._auth = BotcoinAuth(agent_id, private_key, public_key)
        instance._name = None
        return instance

    @property
    def agent_id(self) -> str:
        """Get the agent's ID."""
        return self._auth.agent_id if self._auth else ""

    def _post(self, endpoint: str, payload: dict) -> dict:
        """Make authenticated POST request."""
        if not self._auth:
            raise AuthError("No credentials — create agent or load credentials first")

        body = json.dumps(payload)
        headers = self._auth.headers(body)
        r = requests.post(f"{self.api_url}{endpoint}", data=body, headers=headers)

        if r.status_code == 401:
            raise AuthError("Authentication failed — check credentials")
        if r.status_code == 404:
            raise NotFoundError(f"Resource not found: {endpoint}")
        if r.status_code == 402:
            raise InsufficientFundsError("Not enough $BOTC for this operation")
        if r.status_code != 200:
            raise BotcoinError(f"API error {r.status_code}: {r.text}")

        return r.json()

    def _get(self, endpoint: str, authenticated: bool = False) -> dict:
        """Make GET request (optionally authenticated)."""
        headers = {}
        if authenticated and self._auth:
            headers["X-Agent-Id"] = self._auth.agent_id

        r = requests.get(f"{self.api_url}{endpoint}", headers=headers)

        if r.status_code != 200:
            raise BotcoinError(f"API error {r.status_code}: {r.text}")

        return r.json()

    # ─── Memory ──────────────────────────────────────────────────────────────

    def remember(
        self,
        content: str,
        tags: list[str] = None,
        memory_type: str = "fact",
        domains: list[str] = None,
        importance: float = 0.5,
    ) -> Memory:
        """
        Store a memory.

        Args:
            content: The memory content
            tags: Tags for categorization
            memory_type: One of: fact, episode, preference, skill, relationship
            domains: Knowledge domains
            importance: Importance score (0.0-1.0)

        Returns:
            Memory object with id, content, tags, etc.
        """
        data = self._post(
            "/api/v1/memory/store",
            {
                "content": content,
                "tags": tags or [],
                "memory_type": memory_type,
                "domains": domains or [],
                "importance": importance,
            },
        )
        return Memory.from_api(data.get("memory", data))

    def search(self, query: str, limit: int = 5, domains: list[str] = None) -> list[SearchResult]:
        """
        Search your memories.

        Args:
            query: Search query
            limit: Maximum results (1-100)
            domains: Filter by domains

        Returns:
            List of SearchResult objects
        """
        data = self._post(
            "/api/v1/memory/recall",
            {
                "query": query,
                "limit": limit,
                "domains": domains or [],
            },
        )
        return [SearchResult.from_api(m) for m in data.get("memories", [])]

    def get_memory(self, memory_id: str) -> Memory:
        """Get a specific memory by ID."""
        data = self._get(f"/api/v1/memory/{memory_id}", authenticated=True)
        return Memory.from_api(data.get("memory", data))

    # ─── Marketplace ─────────────────────────────────────────────────────────

    def marketplace(
        self,
        query: str,
        limit: int = 5,
        domains: list[str] = None,
        max_price: float = None,
    ) -> list[Listing]:
        """
        Search the knowledge marketplace.

        Args:
            query: Search query
            limit: Maximum results
            domains: Filter by domains
            max_price: Maximum price filter

        Returns:
            List of Listing objects
        """
        payload = {
            "query": query,
            "limit": limit,
            "domains": domains or [],
        }
        if max_price is not None:
            payload["max_price"] = max_price

        data = self._post("/api/v1/knowledge/query", payload)
        signals = data.get("signals", data.get("available", []))
        return [Listing.from_api(item) for item in signals]

    def purchase(self, listing_id: str, max_price: float = None) -> Purchase:
        """
        Purchase knowledge from the marketplace.

        Args:
            listing_id: Signal/listing ID to purchase
            max_price: Maximum price willing to pay

        Returns:
            Purchase object with content and covenant split
        """
        payload = {"signal_id": listing_id}
        if max_price is not None:
            payload["max_price"] = max_price

        data = self._post("/api/v1/knowledge/purchase", payload)
        return Purchase.from_api(data)

    def publish(
        self,
        memory_id: str,
        title: str,
        price: float,
        description: str = "",
        domains: list[str] = None,
    ) -> Signal:
        """
        Publish a memory to the marketplace.

        Args:
            memory_id: ID of memory to publish
            title: Listing title
            price: Price in $BOTC
            description: Listing description
            domains: Knowledge domains

        Returns:
            Signal object with id and url
        """
        data = self._post(
            "/api/v1/memory/share",
            {
                "memory_id": memory_id,
                "title": title,
                "price": price,
                "description": description or title,
                "domains": domains or [],
            },
        )
        return Signal.from_api(data)

    # ─── Knowledge Graph ─────────────────────────────────────────────────────

    def create_entity(
        self,
        name: str,
        entity_type: str = "concept",
        properties: dict = None,
    ) -> Entity:
        """
        Create an entity in the knowledge graph.

        Args:
            name: Entity name
            entity_type: Type (concept, tool, person, etc.)
            properties: Additional properties

        Returns:
            Entity object
        """
        data = self._post(
            "/api/v1/entity/create",
            {
                "name": name,
                "entity_type": entity_type,
                "properties": properties or {},
            },
        )
        return Entity.from_api(data)

    def create_relation(
        self,
        source: str,
        target: str,
        relation_type: str = "related_to",
    ) -> dict:
        """
        Create a relation between entities.

        Args:
            source: Source entity name
            target: Target entity name
            relation_type: Relation type (related_to, uses, treats, etc.)

        Returns:
            API response dict
        """
        return self._post(
            "/api/v1/relation/create",
            {
                "source": source,
                "target": target,
                "relation_type": relation_type,
            },
        )

    def graph(self, entity_name: str) -> dict:
        """
        Get the knowledge graph for an entity.

        Args:
            entity_name: Entity to get graph for

        Returns:
            Graph data with nodes and edges
        """
        return self._get(f"/api/v1/entity/graph?name={entity_name}", authenticated=True)

    # ─── Info ────────────────────────────────────────────────────────────────

    @property
    def balance(self) -> float:
        """Get agent's $BOTC balance."""
        if not self._auth:
            return 0.0
        data = self._get(f"/api/v1/agents/{self._auth.agent_id}", authenticated=True)
        return float(data.get("balance", data.get("total_earned", 0.0)))

    def stats(self) -> Stats:
        """Get network dashboard stats."""
        data = self._get("/api/v1/stats")
        return Stats.from_api(data)

    def health(self) -> dict:
        """Check API health."""
        r = requests.get(f"{self.api_url}/health")
        return r.json()

    def __repr__(self) -> str:
        agent_id = self._auth.agent_id if self._auth else "not-connected"
        return f"Botcoin(agent_id={agent_id!r})"
