import base64
import logging
import os

from .base import BaseProvider, Thought

logger = logging.getLogger("OpenCastor.Anthropic")


class AnthropicProvider(BaseProvider):
    """Anthropic Claude adapter. Optimized for complex reasoning and safety."""

    # Default to the latest Claude model when none specified in config
    DEFAULT_MODEL = "claude-opus-4-6"

    # Claude setup-token prefix (generated by `claude setup-token`)
    SETUP_TOKEN_PREFIX = "sk-ant-oat01-"

    def __init__(self, config):
        # Apply default model before super().__init__ reads it
        if not config.get("model") or config.get("model") == "default-model":
            config["model"] = self.DEFAULT_MODEL
        super().__init__(config)
        import anthropic

        # Resolution order:
        # 1. OpenCastor's own stored token (~/.opencastor/anthropic-token)
        #    — This takes priority because it's explicitly set via `castor login`
        # 2. ANTHROPIC_API_KEY env var
        # 3. config api_key
        #
        # NOTE: We do NOT read from ~/.claude/.credentials.json — that belongs
        # to Claude CLI / OpenClaw. Reading it risks token invalidation (the
        # "token sink" problem where refreshing one client's token kills another's).
        api_key = self._read_stored_token()

        if not api_key:
            api_key = os.getenv("ANTHROPIC_API_KEY") or config.get("api_key")

        if not api_key:
            raise ValueError(
                "No Anthropic credentials found. Options:\n"
                "  1. Run 'castor login anthropic' to generate a setup-token\n"
                "     (uses your Claude Max/Pro subscription — no per-token billing)\n"
                "  2. Set ANTHROPIC_API_KEY to an API key from console.anthropic.com\n"
                "  3. Run 'castor wizard' to configure interactively"
            )

        if api_key.startswith(self.SETUP_TOKEN_PREFIX):
            # OAuth setup-token — route through Claude CLI (handles OAuth
            # token exchange internally). This mirrors how OpenClaw uses
            # Claude Max/Pro subscriptions.
            from castor.claude_proxy import ClaudeOAuthClient

            logger.info("Using Claude subscription via CLI (OAuth token)")
            self._use_cli = True
            self._cli_client = ClaudeOAuthClient(api_key)
            self.client = None
        else:
            logger.info("Using Anthropic API key")
            self._use_cli = False
            self.client = anthropic.Anthropic(api_key=api_key)

    # Path for OpenCastor's own token store (separate from Claude CLI / OpenClaw)
    TOKEN_PATH = os.path.expanduser("~/.opencastor/anthropic-token")

    @classmethod
    def _read_stored_token(cls):
        """Read OpenCastor's own stored Anthropic token.

        Stored at ~/.opencastor/anthropic-token by 'castor login anthropic'.
        This is intentionally separate from ~/.claude/.credentials.json
        to avoid the token sink problem (invalidating OpenClaw's token).
        """
        try:
            if os.path.exists(cls.TOKEN_PATH):
                with open(cls.TOKEN_PATH) as f:
                    token = f.read().strip()
                if token:
                    logger.debug("Read Anthropic token from %s", cls.TOKEN_PATH)
                    return token
        except Exception as e:
            logger.debug(f"Could not read stored token: {e}")
        return None

    @classmethod
    def save_token(cls, token: str) -> str:
        """Save an Anthropic token to OpenCastor's token store.

        Returns the path where the token was saved.
        """
        token_dir = os.path.dirname(cls.TOKEN_PATH)
        os.makedirs(token_dir, mode=0o700, exist_ok=True)
        with open(cls.TOKEN_PATH, "w") as f:
            f.write(token)
        os.chmod(cls.TOKEN_PATH, 0o600)
        return cls.TOKEN_PATH

    def think(self, image_bytes: bytes, instruction: str) -> Thought:
        b64_image = base64.b64encode(image_bytes).decode("utf-8")

        # Route through CLI if using OAuth token
        if getattr(self, "_use_cli", False):
            return self._think_via_cli(instruction)

        # Build message content -- include image only if it has real data
        content = []
        is_blank = image_bytes == b"\x00" * len(image_bytes)
        if not is_blank and len(image_bytes) > 100:
            content.append(
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/jpeg",
                        "data": b64_image,
                    },
                }
            )
        content.append({"type": "text", "text": instruction})

        try:
            response = self.client.messages.create(
                model=self.model_name,
                max_tokens=1024,
                system=self.system_prompt,
                messages=[{"role": "user", "content": content}],
            )
            text = response.content[0].text
            action = self._clean_json(text)
            return Thought(text, action)
        except Exception as e:
            logger.error(f"Anthropic error: {e}")
            return Thought(f"Error: {e}", None)

    def _think_via_cli(self, instruction: str) -> Thought:
        """Call Claude via OAuth CLI client (uses Max/Pro subscription)."""
        try:
            response = self._cli_client.create_message(
                model=self.model_name,
                system=self.system_prompt,
                messages=[{"role": "user", "content": instruction}],
                max_tokens=1024,
            )
            text = response["content"][0]["text"]
            action = self._clean_json(text)
            return Thought(text, action)
        except Exception as e:
            logger.error(f"CLI error: {e}")
            return Thought(f"Error: {e}", None)
