#!/usr/bin/env python3
# File: src/noveler/infrastructure/repositories/configuration_repository.py
# Purpose: Provide configuration discovery and lookup utilities for legacy adapters.
# Context: Infrastructure layer repository accessed by CLI helpers and tests expecting Japanese config assets.

"""Configuration repository capable of loading Japanese-labelled project files.

The repository supports legacy workflows that store configuration in
``プロジェクト設定.yaml`` while also integrating with the hierarchical configuration
adapter when present. Returned paths are normalised to POSIX form so that tests
behave consistently on Windows and POSIX environments.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any

import yaml

from noveler.infrastructure.adapters.hierarchical_config_adapter import HierarchicalConfig
from noveler.infrastructure.config.config_manager import get_config_manager

_CONFIG_MANAGER = get_config_manager()

PROJECT_CONFIG_FILENAME = "プロジェクト設定.yaml"
DEFAULT_GUIDE_DIRNAME = "00_ガイド"


class IConfigurationRepository(ABC):
    """Abstract repository contract for project configuration access."""

    @abstractmethod
    def find_project_config(self, start_path: str | Path | None = None) -> Path | None:
        """Locate the nearest ``プロジェクト設定.yaml`` file."""

    @abstractmethod
    def load_project_config(self, config_path: str | Path) -> dict[str, Any]:
        """Load the project configuration payload from disk."""

    @abstractmethod
    def get_project_paths(self) -> dict[str, str]:
        """Return project and guide paths with environment-aware precedence."""

    @abstractmethod
    def get_project_info(self) -> dict[str, Any]:
        """Return project metadata such as title, genre, and identifiers."""

    @abstractmethod
    def get_config(self, key: str | None = None, default: object = None) -> object:
        """Read a configuration value with optional dot-path traversal."""

    @abstractmethod
    def setup_environment(self) -> bool:
        """Populate environment variables derived from project settings."""

    @abstractmethod
    def get_ncode(self) -> str | None:
        """Return the NCODE identifier when available."""

    @abstractmethod
    def get_author_info(self) -> dict[str, str]:
        """Return author metadata for legacy workflows."""

    @abstractmethod
    def get_quality_threshold(self) -> int:
        """Return the score threshold used by the quality workflow."""


class ConfigurationRepository(IConfigurationRepository):
    """Concrete repository that bridges legacy YAML files and hierarchical config."""

    def __init__(self) -> None:
        try:
            self._hierarchical_config = HierarchicalConfig()
            self._has_hierarchical = True
        except Exception:
            self._hierarchical_config = None
            self._has_hierarchical = False

    def _to_posix(self, path: str) -> str:
        """Normalize the supplied filesystem path to a POSIX-style string."""
        return Path(path).as_posix()

    def find_project_config(self, start_path: str | Path | None = None) -> Path | None:
        """Walk parent directories until a ``プロジェクト設定.yaml`` file is found."""
        search_root = Path(start_path) if start_path is not None else Path.cwd()
        current = search_root.resolve()

        while True:
            candidate = current / PROJECT_CONFIG_FILENAME
            if candidate.exists():
                return candidate
            if current.parent == current:
                break
            current = current.parent

        return None

    def load_project_config(self, config_path: str | Path | None = None) -> dict[str, Any]:
        """Load YAML configuration from the discovered project file."""
        resolved_path = Path(config_path) if config_path is not None else self.find_project_config()
        if resolved_path is None or not Path(resolved_path).exists():
            return {}

        try:
            with Path(resolved_path).open(encoding="utf-8") as handle:
                loaded = yaml.safe_load(handle)
        except Exception:
            return {}

        return loaded if isinstance(loaded, dict) else {}

    def get_project_paths(self) -> dict[str, str]:
        """Resolve project and guide paths honouring env vars, config, and discovery."""
        paths: dict[str, str] = {}

        env_project = _CONFIG_MANAGER.get("PROJECT_ROOT", default=None)
        if env_project:
            paths["project_root"] = self._to_posix(env_project)
            guide_env = _CONFIG_MANAGER.get("GUIDE_ROOT", default=None)
            if guide_env:
                paths["guide_root"] = self._to_posix(guide_env)
            else:
                default_guide = Path(env_project).parent / DEFAULT_GUIDE_DIRNAME
                paths["guide_root"] = self._to_posix(str(default_guide))
            return paths

        config = self.load_project_config()
        path_settings = config.get("paths", {}) if isinstance(config, dict) else {}
        config_project = path_settings.get("project_root")
        if config_project:
            paths["project_root"] = self._to_posix(config_project)
            guide_from_config = path_settings.get("guide_root")
            if guide_from_config:
                paths["guide_root"] = self._to_posix(guide_from_config)
            else:
                default_guide = Path(config_project).parent / DEFAULT_GUIDE_DIRNAME
                paths["guide_root"] = self._to_posix(str(default_guide))
            return paths

        discovered_config = self.find_project_config()
        if discovered_config:
            project_root = discovered_config.parent
            paths["project_root"] = self._to_posix(str(project_root))
            default_guide = project_root.parent / DEFAULT_GUIDE_DIRNAME
            paths["guide_root"] = self._to_posix(str(default_guide))
            return paths

        return paths

    def setup_environment(self) -> bool:
        """Populate ``PROJECT_ROOT`` and ``GUIDE_ROOT`` when available."""
        paths = self.get_project_paths()
        if not paths:
            return False

        if not _CONFIG_MANAGER.get("PROJECT_ROOT", default=None):
            _CONFIG_MANAGER.set("PROJECT_ROOT", paths.get("project_root", ""), persist_env=True)
        _CONFIG_MANAGER.set("GUIDE_ROOT", paths.get("guide_root", ""), persist_env=True)
        return True

    def get_project_info(self) -> dict[str, Any]:
        """Return project metadata, preferring hierarchical config when available."""
        if self._has_hierarchical:
            return self._hierarchical_config.get("project", {})
        return self.load_project_config().get("project", {})

    def get_ncode(self) -> str | None:
        """Return the configured NCODE identifier, if present."""
        project_info = self.get_project_info()
        return project_info.get("ncode") if isinstance(project_info, dict) else None

    def get_config(self, key: str | None = None, default: object = None) -> object:
        """Read configuration values via hierarchical config or raw dict traversal."""
        if self._has_hierarchical:
            return self._hierarchical_config.get(key, default)

        config = self.load_project_config()
        if key is None:
            return config

        current: object = config
        for part in key.split("."):
            if isinstance(current, dict) and part in current:
                current = current[part]
            else:
                return default
        return current

    def get_author_info(self) -> dict[str, str]:
        """Return author metadata, falling back to hierarchical defaults when required."""
        if self._has_hierarchical:
            author = self._hierarchical_config.get("author", {})
            if author and author.get("pen_name"):
                return author
            return self._hierarchical_config.get("default_author", {})

        return self.load_project_config().get("author", {})

    def get_quality_threshold(self) -> int:
        """Return the configured quality threshold or the legacy default."""
        if self._has_hierarchical:
            return self._hierarchical_config.get("quality_management.default_threshold", 80)

        settings = self.load_project_config().get("settings", {})
        if isinstance(settings, dict):
            return int(settings.get("quality_threshold", 80))
        return 80
