import logging
import os
import re
from pathlib import Path
from typing import Any, Optional

import yaml

ENV_VAR_MATCHER = re.compile(r"\$\{(\w+)\}")
logger = logging.getLogger(__name__)


def substitute_env_vars(config_str: Any) -> str:
    """Substitute environment variables in a string."""
    if not isinstance(config_str, str):
        return "" if config_str is None else str(config_str)

    def replace(match: re.Match[str]) -> str:
        return os.getenv(match.group(1), "")

    return ENV_VAR_MATCHER.sub(replace, config_str)


class DatabaseConfig:
    """Database connection configuration."""

    def __init__(self, name: str, config: dict[str, Any]):
        self.name = name
        self.type = config.get("type")  # postgresql, mongodb
        self.host = config.get("host")
        self.port = config.get("port")
        self.database = config.get("database")
        self.username = config.get("username")
        self.password = config.get("password")

    def get_connection_string(self) -> str:
        """Generate connection string for the database."""
        if self.type == "postgresql" or self.type == "postgres":
            return (
                f"postgresql://{self.username}:{self.password}@"
                f"{self.host}:{self.port}/{self.database}"
            )
        elif self.type == "mongodb" or self.type == "mongo":
            return (
                f"mongodb://{self.username}:{self.password}@"
                f"{self.host}:{self.port}/{self.database}"
            )
        else:
            raise ValueError(f"Unsupported database type: {self.type}")


class LLMProviderConfig:
    """LLM provider configuration."""

    def __init__(self, name: str, config: dict[str, Any]):
        self.name = name
        self.api_key = substitute_env_vars(config.get("api_key", ""))
        self.model = config.get("model")
        self.project = substitute_env_vars(config.get("project", ""))
        self.region = substitute_env_vars(config.get("region", ""))
        self.temperature = config.get("temperature", 0.7)
        self.max_tokens = config.get("max_tokens", 2048)


class IQToolkitConfig:
    """Main configuration manager for IQToolkit."""

    def __init__(self, config_data: dict[str, Any]):
        self.default_provider = config_data.get("default_provider", "openai")

        # Load providers (supports 'providers' and legacy 'llm_providers' keys)
        self.providers: dict[str, LLMProviderConfig] = {}
        providers_config = config_data.get(
            "providers", config_data.get("llm_providers", {})
        )
        for provider_name, provider_config in providers_config.items():
            self.providers[provider_name] = LLMProviderConfig(
                provider_name, provider_config
            )

        # Load databases
        self.databases: dict[str, DatabaseConfig] = {}
        databases_config = config_data.get("databases", {})
        for db_name, db_config in databases_config.items():
            self.databases[db_name] = DatabaseConfig(db_name, db_config)

        # Load simple config options (backward compatibility)
        self.openai_api_key = substitute_env_vars(config_data.get("openai_api_key", ""))
        self.llm_provider = config_data.get("llm_provider", "openai")
        self.openai_model = config_data.get("openai_model", "gpt-4o")
        self.ollama_model = config_data.get("ollama_model", "mistral")
        self.ollama_host = config_data.get("ollama_host", "http://localhost:11434")
        self.log_format = config_data.get("log_format", "plain")
        self.top_n = int(config_data.get("top_n", 5))
        self.output = config_data.get("output", "")

    def validate(self) -> list[str]:
        """Validate configuration and return a list of issues."""
        issues: list[str] = []
        allowed_db_types = {"postgresql", "postgres", "mongodb", "mongo"}
        allowed_log_formats = {"plain", "csv", "json"}

        if self.providers and self.default_provider not in self.providers:
            issues.append(
                "default_provider "
                f"'{self.default_provider}' is not defined in providers"
            )

        cloud_providers = {
            "openai",
            "anthropic",
            "google",
            "gemini",
            "azure",
            "bedrock",
            "vertex",
        }
        for provider_name, provider in self.providers.items():
            if not provider.model:
                issues.append(f"Provider '{provider_name}' is missing a model")
            if provider_name == "vertex":
                if not provider.project and not os.getenv("GCP_PROJECT"):
                    issues.append("Provider 'vertex' is missing a project")
                if not provider.region and not os.getenv("GCP_REGION"):
                    issues.append("Provider 'vertex' is missing a region")
                continue
            if provider_name in cloud_providers and not provider.api_key:
                issues.append(f"Provider '{provider_name}' is missing an api_key")

        for db_name, db in self.databases.items():
            if db.type not in allowed_db_types:
                issues.append(f"Database '{db_name}' has unsupported type '{db.type}'")
                continue

            if db.type in {"postgresql", "postgres"}:
                missing: list[str] = []
                if db.host in (None, ""):
                    missing.append("host")
                if db.port in (None, ""):
                    missing.append("port")
                if db.database in (None, ""):
                    missing.append("database")
                if db.username in (None, ""):
                    missing.append("username")
                if db.password in (None, ""):
                    missing.append("password")
                if missing:
                    issues.append(
                        f"Database '{db_name}' is missing required fields: "
                        f"{', '.join(missing)}"
                    )

            if db.type in {"mongodb", "mongo"}:
                missing = []
                if db.host in (None, ""):
                    missing.append("host")
                if db.port in (None, ""):
                    missing.append("port")
                if db.database in (None, ""):
                    missing.append("database")
                if missing:
                    issues.append(
                        f"Database '{db_name}' is missing required fields: "
                        f"{', '.join(missing)}"
                    )

        if self.log_format not in allowed_log_formats:
            issues.append("log_format must be one of: plain, csv, json")

        if self.top_n <= 0:
            issues.append("top_n must be greater than 0")

        return issues

    @staticmethod
    def load(config_path: str = ".iqtoolkit-analyzer.yml") -> "IQToolkitConfig":
        """Load configuration from YAML file."""
        path = Path(config_path).expanduser()
        if path.exists():
            with path.open("r", encoding="utf-8") as f:
                config_data = yaml.safe_load(f) or {}
        else:
            config_data = {}

        config = IQToolkitConfig(config_data)
        issues = config.validate()
        if issues:
            logger.warning(
                "Configuration validation issues found (%d). Run the config validate "
                "command for details.",
                len(issues),
            )
        return config

    def get_database(self, name: str) -> Optional[DatabaseConfig]:
        """Get database configuration by name."""
        return self.databases.get(name)

    def get_provider(self, name: str) -> Optional[LLMProviderConfig]:
        """Get LLM provider configuration by name."""
        return self.providers.get(name)

    def list_databases(self) -> list[str]:
        """List all configured database names."""
        return list(self.databases.keys())

    def list_providers(self) -> list[str]:
        """List all configured providers."""
        return list(self.providers.keys())
