from __future__ import annotations
from enum import Enum
import os
from pathlib import Path
from pyexpat import model
from tkinter import ON
from typing import Any
from click import File, command
from cyclopts import App
from pydantic import BaseModel, Field, PrivateAttr, model_validator


class ModelConfig(BaseModel):
    name: str = "mistralai/devstral-2512:free"
    temperature: float = Field(default=1.0, ge=0.0, le=2.0)
    context_window: int = 256_000


class ShellEnvironmentPolicy(BaseModel):
    ignore_default_excludes: bool = False
    exclude_patterns: list[str] = Field(
        default_factory=lambda: ["*KEY*", "*TOKEN*", "*SECRET*"]
    )
    set_vars: dict[str, str] = Field(default_factory=dict)


class MCPServerConfig(BaseModel):
    enabled: bool = True
    startup_timeout_sec: float = 10

    # stdio transport
    command: str | None = None
    args: list[str] = Field(default_factory=list)
    env: dict[str, str] = Field(default_factory=dict)
    cwd: Path | None = None

    # http/sse transport
    url: str | None = None

    @model_validator(mode="after")
    def validate_transport(self) -> MCPServerConfig:
        has_command = self.command is not None
        has_url = self.url is not None

        if not has_command and not has_url:
            raise ValueError(
                "MCP Server must have either 'command' (stdio) or 'url' (http/sse)"
            )

        if has_command and has_url:
            raise ValueError(
                "MCP Server cannot have both 'command' (stdio) and 'url' (http/sse)"
            )

        return self


class ApprovalPolicy(str, Enum):
    ON_REQUEST = "on_request"
    ALWAYS = "always"
    ON_FAILURE = "on_failure"
    AUTO_APPROVE = "auto_approve"
    AUTO_EDIT = "auto_edit"
    NEVER = "never"
    YOLO = "yolo"


class HookTrigger(str, Enum):
    BEFORE_TOOL = "before_tool"
    AFTER_TOOL = "after_tool"
    BEFORE_AGENT = "before_agent"
    AFTER_AGENT = "after_agent"
    ON_ERROR = "on_error"


class HookConfig(BaseModel):
    name: str
    trigger: HookTrigger
    command: str | None = None
    script: str | None = None
    time_out_sec: float = 30.0
    enabled: bool = True

    @model_validator(mode="after")
    def validate_hook(self) -> HookConfig:
        if self.command is None and self.script is None:
            raise ValueError("Hook must have either 'command' or 'script' defined.")

        return self


class Config(BaseModel):
    model_config = {"populate_by_name": True}

    model: ModelConfig = Field(default_factory=ModelConfig)
    cwd: Path = Field(default_factory=Path.cwd)
    shell_environment: ShellEnvironmentPolicy = Field(
        default_factory=ShellEnvironmentPolicy
    )
    max_turns: int = 72
    max_tool_output_tokens: int = 50_000
    mcp_servers: dict[str, MCPServerConfig] = Field(default_factory=dict)
    developer_instructions: str | None = None
    user_instructions: str | None = None
    approval: ApprovalPolicy = ApprovalPolicy.ON_REQUEST
    hooks_enabled: bool = True
    hooks: list[HookConfig] = Field(default_factory=list)
    allowed_tools: list[str] | None = Field(
        None,
        description="If set, only these tools will be available to the agent",
    )

    debug: bool = False

    # API credentials - stored in config.toml or environment variables
    api_key: str | None = Field(default=None)
    api_base_url: str | None = Field(default=None)

    def get_api_key(self) -> str | None:
        """Get API key from config or environment variable."""
        return self.api_key or os.environ.get("API_KEY")

    def get_api_base_url(self) -> str | None:
        """Get API base URL from config or environment variable."""
        return self.api_base_url or os.environ.get("API_BASE_URL")

    @property
    def model_name(self) -> str:
        return self.model.name

    @model_name.setter
    def model_name(self, value: str) -> None:
        self.model.name = value

    @property
    def temperature(self) -> float:
        return self.model.temperature

    @temperature.setter
    def temperature(self, value: float) -> None:
        self.model.temperature = value

    def validate(self) -> None:
        errors: list[str] = []
        if self.get_api_key() is None:
            errors.append(
                "API_KEY is not configured. Set it in config.toml or as an environment variable."
            )
        if not self.cwd.exists() or not self.cwd.is_dir():
            errors.append(f"CWD path does not exist or is not a directory: {self.cwd}")

        return errors

    def to_dict(self) -> dict[str, Any]:
        return self.model_dump(mode="json")
