"""
Base configuration classes.

Provides base classes and common functionality for all configuration
management across the MarkiTect project.
"""

import os
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, Any, Optional, List, Union

from .exceptions import ConfigurationError, ConfigValidationError
from .loaders import load_env_file, get_env_var, resolve_path


@dataclass
class BaseConfig(ABC):
    """Base configuration class with common functionality.

    Provides a foundation for all configuration classes with:
    - Environment variable loading with standardized prefixes
    - File-based configuration loading
    - Validation and error handling
    - Backward compatibility support
    """

    def __post_init__(self):
        """Initialize configuration after dataclass creation."""
        self.load_from_environment()
        self.load_from_files()
        self.validate()

    @abstractmethod
    def get_env_prefix(self) -> str:
        """Get the environment variable prefix for this configuration.

        Returns:
            Environment variable prefix (e.g., 'MARKITECT_', 'TDDAI_')
        """
        pass

    @abstractmethod
    def get_config_files(self) -> List[Union[str, Path]]:
        """Get list of configuration files to load (in order of priority).

        Returns:
            List of configuration file paths to attempt loading
        """
        pass

    @abstractmethod
    def validate(self) -> None:
        """Validate configuration values.

        Raises:
            ConfigValidationError: If validation fails
        """
        pass

    def load_from_environment(self) -> None:
        """Load configuration from environment variables.

        Uses the prefix from get_env_prefix() to find relevant variables.
        """
        prefix = self.get_env_prefix()

        # Get all environment variables with the prefix
        env_vars = {
            key[len(prefix):].lower(): value
            for key, value in os.environ.items()
            if key.startswith(prefix)
        }

        # Apply environment variables to configuration fields
        for field_name, value in env_vars.items():
            if hasattr(self, field_name):
                # Type conversion based on field type
                field_type = self.__dataclass_fields__[field_name].type
                try:
                    if field_type == bool:
                        converted_value = value.lower() in ('true', '1', 'yes', 'on')
                    elif field_type == int:
                        converted_value = int(value)
                    elif field_type == Path:
                        converted_value = resolve_path(value)
                    else:
                        converted_value = value

                    setattr(self, field_name, converted_value)

                except (ValueError, TypeError) as e:
                    raise ConfigurationError(
                        f"Invalid value for {prefix}{field_name.upper()}: {value}",
                        field=field_name,
                        value=value,
                        suggestion=f"Provide a valid {field_type.__name__} value"
                    ) from e

    def load_from_files(self) -> None:
        """Load configuration from files.

        Loads from files specified by get_config_files() in order,
        with later files overriding earlier ones.
        """
        for config_file in self.get_config_files():
            try:
                file_vars = load_env_file(config_file, required=False)

                # Apply file variables (convert to lowercase for consistency)
                prefix = self.get_env_prefix()
                legacy_mapping = self.get_legacy_mapping()

                for key, value in file_vars.items():
                    field_name = None

                    # Check if it's a legacy variable we can map
                    if key in legacy_mapping:
                        field_name = legacy_mapping[key]
                    # Remove prefix if present in file
                    elif key.startswith(prefix):
                        field_name = key[len(prefix):].lower()
                    else:
                        field_name = key.lower()

                    if field_name and hasattr(self, field_name):
                        # Type conversion
                        field_type = self.__dataclass_fields__[field_name].type
                        try:
                            if field_type == bool:
                                converted_value = value.lower() in ('true', '1', 'yes', 'on')
                            elif field_type == int:
                                converted_value = int(value)
                            elif field_type == Path:
                                converted_value = resolve_path(value)
                            else:
                                converted_value = value

                            setattr(self, field_name, converted_value)

                        except (ValueError, TypeError) as e:
                            raise ConfigurationError(
                                f"Invalid value in {config_file} for {field_name}: {value}",
                                field=field_name,
                                value=value
                            ) from e

            except ConfigurationError:
                # Re-raise configuration errors
                raise
            except Exception as e:
                # Convert other errors to configuration errors
                raise ConfigurationError(
                    f"Failed to load configuration from {config_file}: {e}",
                    context={'file': str(config_file)}
                ) from e

    def validate_required_fields(self, *field_names: str) -> None:
        """Validate that required fields are not empty.

        Args:
            *field_names: Names of fields to validate

        Raises:
            ConfigValidationError: If any required field is empty
        """
        for field_name in field_names:
            value = getattr(self, field_name, None)

            if not value or (isinstance(value, str) and not value.strip()):
                raise ConfigValidationError(
                    f"Required configuration field cannot be empty: {field_name}",
                    field=field_name,
                    value=value,
                    suggestion=f"Set {self.get_env_prefix()}{field_name.upper()} environment variable or add to config file"
                )

    def validate_url(self, field_name: str) -> None:
        """Validate that a field contains a valid URL.

        Args:
            field_name: Name of the field to validate

        Raises:
            ConfigValidationError: If URL is invalid
        """
        url = getattr(self, field_name, None)

        if not url:
            return  # Empty URLs are handled by validate_required_fields

        if not isinstance(url, str):
            raise ConfigValidationError(
                f"URL field must be a string: {field_name}",
                field=field_name,
                value=url
            )

        # Basic URL validation
        if not (url.startswith('http://') or url.startswith('https://')):
            raise ConfigValidationError(
                f"URL must start with http:// or https://: {field_name}",
                field=field_name,
                value=url,
                suggestion="Add http:// or https:// prefix to the URL"
            )

    def validate_path_exists(self, field_name: str, create_if_missing: bool = False) -> None:
        """Validate that a path exists.

        Args:
            field_name: Name of the field to validate
            create_if_missing: Whether to create the path if it doesn't exist

        Raises:
            ConfigValidationError: If path doesn't exist and cannot be created
        """
        path = getattr(self, field_name, None)

        if not path:
            return  # Empty paths are handled by validate_required_fields

        path = Path(path)

        if not path.exists():
            if create_if_missing:
                try:
                    path.mkdir(parents=True, exist_ok=True)
                except OSError as e:
                    raise ConfigValidationError(
                        f"Cannot create directory: {path}",
                        field=field_name,
                        value=str(path),
                        suggestion="Check directory permissions and parent directory existence"
                    ) from e
            else:
                raise ConfigValidationError(
                    f"Path does not exist: {path}",
                    field=field_name,
                    value=str(path),
                    suggestion=f"Create the directory {path} or use a different path"
                )

    def to_dict(self) -> Dict[str, Any]:
        """Convert configuration to dictionary.

        Returns:
            Dictionary representation of configuration
        """
        result = {}
        for field_name, field_def in self.__dataclass_fields__.items():
            value = getattr(self, field_name)

            # Convert Path objects to strings
            if isinstance(value, Path):
                value = str(value)

            result[field_name] = value

        return result

    def get_legacy_mapping(self) -> Dict[str, str]:
        """Get mapping of legacy environment variable names to new names.

        Override in subclasses to provide backward compatibility.

        Returns:
            Dictionary mapping old environment variable names to new field names
        """
        return {}

    def load_legacy_environment(self) -> None:
        """Load configuration from legacy environment variables.

        Provides backward compatibility for existing environment variable names.
        """
        legacy_mapping = self.get_legacy_mapping()

        for old_var, field_name in legacy_mapping.items():
            if old_var in os.environ and hasattr(self, field_name):
                value = os.environ[old_var]

                # Type conversion
                field_type = self.__dataclass_fields__[field_name].type
                try:
                    if field_type == bool:
                        converted_value = value.lower() in ('true', '1', 'yes', 'on')
                    elif field_type == int:
                        converted_value = int(value)
                    elif field_type == Path:
                        converted_value = resolve_path(value)
                    else:
                        converted_value = value

                    setattr(self, field_name, converted_value)

                except (ValueError, TypeError) as e:
                    raise ConfigurationError(
                        f"Invalid value for legacy environment variable {old_var}: {value}",
                        field=field_name,
                        value=value,
                        suggestion=f"Use new environment variable {self.get_env_prefix()}{field_name.upper()} instead"
                    ) from e