"""Cleave settings management.

Settings are stored in ~/.cleave/settings.yaml and control:
- cloven_report_path: Directory for .cloven.md reports (default: workspace)
- cloven_report_naming: How to name reports (default: "descriptor")

Naming options:
- "descriptor": <descriptor>.cloven.md (extracted from directive)
- "timestamp": <ISO8601>.cloven.md
- "workspace": .cloven.md (legacy, in workspace)
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from cleave.core.yaml_utils import parse_yaml_simple, to_yaml
from cleave.core.file_utils import atomic_write_text


class SettingsValidationError(Exception):
    """Raised when settings validation fails."""

    pass


# Default settings directory
SETTINGS_DIR = Path.home() / ".cleave"
SETTINGS_FILE = SETTINGS_DIR / "settings.yaml"

# Default values
DEFAULT_SETTINGS = {
    "cloven_report_path": None,  # None means use workspace directory
    "cloven_report_naming": "descriptor",  # descriptor, timestamp, or workspace
}

# Valid enum values
VALID_REPORT_NAMING = ["descriptor", "timestamp", "workspace"]


@dataclass
class CleaveSettings:
    """Cleave configuration settings."""

    cloven_report_path: str | None = None
    cloven_report_naming: str = "descriptor"

    # Future settings can be added here
    _extra: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        """Convert settings to dictionary for serialization."""
        result: dict[str, Any] = {
            "cloven_report_path": self.cloven_report_path,
            "cloven_report_naming": self.cloven_report_naming,
        }
        # Include any extra settings that were loaded
        result.update(self._extra)
        return result

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "CleaveSettings":
        """Create settings from dictionary."""
        known_keys = {"cloven_report_path", "cloven_report_naming"}
        extra = {k: v for k, v in data.items() if k not in known_keys}

        return cls(
            cloven_report_path=data.get("cloven_report_path"),
            cloven_report_naming=data.get("cloven_report_naming", "descriptor"),
            _extra=extra,
        )


def _validate_settings(settings: CleaveSettings) -> None:
    """Validate settings values.

    Args:
        settings: Settings to validate.

    Raises:
        SettingsValidationError: If settings contain invalid values.
    """
    # Validate cloven_report_naming
    if settings.cloven_report_naming not in VALID_REPORT_NAMING:
        raise SettingsValidationError(
            f"Invalid cloven_report_naming: '{settings.cloven_report_naming}'. "
            f"Must be one of: {', '.join(VALID_REPORT_NAMING)}"
        )

    # Validate cloven_report_path if set
    if settings.cloven_report_path is not None:
        path = Path(settings.cloven_report_path).expanduser()
        # Only validate parent exists to avoid creating directories prematurely
        if not path.parent.exists() and str(path.parent) != ".":
            raise SettingsValidationError(
                f"Parent directory of cloven_report_path does not exist: {path.parent}"
            )


def get_settings_dir() -> Path:
    """Get the settings directory, creating if needed."""
    SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
    return SETTINGS_DIR


def get_settings_file() -> Path:
    """Get the settings file path."""
    return SETTINGS_FILE


def load_settings() -> CleaveSettings:
    """Load settings from ~/.cleave/settings.yaml.

    Returns default settings if file doesn't exist.

    Raises:
        SettingsValidationError: If settings contain invalid values.
    """
    if not SETTINGS_FILE.exists():
        return CleaveSettings()

    try:
        content = SETTINGS_FILE.read_text()
        data = parse_yaml_simple(content)
        if not isinstance(data, dict):
            return CleaveSettings()
        settings = CleaveSettings.from_dict(data)
        _validate_settings(settings)
        return settings
    except SettingsValidationError:
        raise
    except Exception:
        # Return defaults on any parsing error
        return CleaveSettings()


def save_settings(settings: CleaveSettings) -> Path:
    """Save settings to ~/.cleave/settings.yaml.

    Args:
        settings: Settings to save.

    Returns:
        Path to the settings file.

    Raises:
        SettingsValidationError: If settings contain invalid values.
    """
    _validate_settings(settings)
    get_settings_dir()  # Ensure directory exists

    content = to_yaml(settings.to_dict())
    atomic_write_text(SETTINGS_FILE, content)
    return SETTINGS_FILE


def get_setting(key: str) -> Any:
    """Get a single setting value."""
    settings = load_settings()
    if hasattr(settings, key):
        return getattr(settings, key)
    return settings._extra.get(key)


def set_setting(key: str, value: Any) -> None:
    """Set a single setting value and save.

    Raises:
        SettingsValidationError: If value is invalid for the key.
    """
    settings = load_settings()

    # Validate specific keys
    if key == "cloven_report_naming":
        if not isinstance(value, str):
            raise SettingsValidationError(
                f"cloven_report_naming must be a string, got {type(value).__name__}"
            )
        if value not in VALID_REPORT_NAMING:
            raise SettingsValidationError(
                f"Invalid cloven_report_naming: '{value}'. "
                f"Must be one of: {', '.join(VALID_REPORT_NAMING)}"
            )
    elif key == "cloven_report_path":
        if value is not None and not isinstance(value, str):
            raise SettingsValidationError(
                f"cloven_report_path must be a string or None, got {type(value).__name__}"
            )
        if value is not None:
            path = Path(value).expanduser()
            if not path.parent.exists():
                raise SettingsValidationError(
                    f"Parent directory of cloven_report_path does not exist: {path.parent}"
                )

    if hasattr(settings, key) and key != "_extra":
        setattr(settings, key, value)
    else:
        settings._extra[key] = value
    save_settings(settings)


def generate_cloven_filename(
    directive: str,
    workspace_path: Path | str | None = None,
    settings: CleaveSettings | None = None,
) -> Path:
    """Generate the cloven report filename based on settings.

    Args:
        directive: The root directive text
        workspace_path: Path to the .cleave workspace (for workspace naming)
        settings: Settings to use (loads from file if not provided)

    Returns:
        Full path to the cloven report file
    """
    if settings is None:
        settings = load_settings()

    # Determine base directory
    if settings.cloven_report_path:
        base_dir = Path(settings.cloven_report_path).expanduser()
        base_dir.mkdir(parents=True, exist_ok=True)
    elif workspace_path:
        base_dir = Path(workspace_path)
    else:
        base_dir = Path.cwd()

    # Generate filename based on naming strategy
    naming = settings.cloven_report_naming

    if naming == "workspace":
        # Legacy: .cloven.md in workspace
        return base_dir / ".cloven.md"

    elif naming == "timestamp":
        # ISO8601 timestamp
        from datetime import datetime
        timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
        return base_dir / f"{timestamp}.cloven.md"

    else:  # "descriptor" (default)
        # Extract meaningful descriptor from directive
        descriptor = _extract_descriptor(directive)
        return base_dir / f"{descriptor}.cloven.md"


def _extract_descriptor(directive: str) -> str:
    """Extract a useful filename descriptor from a directive.

    Takes the first ~50 chars, cleans them for filesystem use.
    """
    # Take first line or first 80 chars
    first_line = directive.split("\n")[0][:80]

    # Remove common prefixes
    prefixes_to_strip = [
        "integrate", "add", "implement", "create", "build", "migrate",
        "update", "fix", "refactor", "the", "a", "an",
    ]
    words = first_line.lower().split()
    while words and words[0] in prefixes_to_strip:
        words.pop(0)

    cleaned = " ".join(words[:6])  # Take up to 6 meaningful words

    # Clean for filesystem: lowercase, replace spaces/special chars with hyphens
    cleaned = re.sub(r"[^a-z0-9\s-]", "", cleaned.lower())
    cleaned = re.sub(r"\s+", "-", cleaned.strip())
    cleaned = re.sub(r"-+", "-", cleaned)  # Collapse multiple hyphens
    cleaned = cleaned[:50].rstrip("-")  # Limit length

    if not cleaned:
        cleaned = "cleave-report"

    return cleaned
