import os
import functools
import json
from pathlib import Path
from typing import Dict, Any

import yaml
import logging
from jsonschema import validate, ValidationError

logger = logging.getLogger(__name__)

def get_base_dir() -> Path:
    """Get the base '.agents' directory."""
    # This file is in src/agent_tools/config.py
    # So base dir is 3 levels up
    return Path(__file__).parent.parent.parent

def get_rules_path() -> Path:
    from agent_tools.context import REPO_CWD
    cwd = REPO_CWD.get() or os.getcwd()
    # 1. PRIORITY 1: Project-level rules (.agent/configs/rules.yaml)
    project_rules = Path(cwd) / ".agent" / "configs" / "rules.yaml"
    if project_rules.exists():
        return project_rules
        
    # 2. PRIORITY 2: User-level global rules (~/.agent/configs/rules.yaml)
    user_rules = Path.home() / ".agent" / "configs" / "rules.yaml"
    if user_rules.exists():
        return user_rules
            
    # 3. PRIORITY 3: Tool-level internal fallback
    return Path(__file__).parent / "configs" / "rules.yaml"

def get_schema_path() -> Path:
    from agent_tools.context import REPO_CWD
    cwd = REPO_CWD.get() or os.getcwd()
    # Same cascading logic for schema or keep it tool-internal?
    # Usually schema is tied to the tool version, but we'll follow similar pattern
    project_schema = Path(cwd) / ".agent" / "configs" / "schema.json"
    if project_schema.exists():
        return project_schema
    return Path(__file__).parent / "configs" / "schema.json"

@functools.lru_cache(maxsize=1)
def load_schema() -> Dict[str, Any]:
    schema_path = get_schema_path()
    if not schema_path.exists():
        return {}
        
    try:
        with open(schema_path, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}

def validate_rules(rules: Dict[str, Any]) -> None:
    schema = load_schema()
    if schema:
        try:
            validate(instance=rules, schema=schema)
        except ValidationError as e:
            logger.warning(f"rules.yaml validation failed: {e.message}")

@functools.lru_cache(maxsize=32)
def _load_rules_cached(rules_path_str: str) -> Dict[str, Any]:
    rules_path = Path(rules_path_str)
    logger.info(f"Attempting to load rules from: {rules_path.absolute()}")
    if not rules_path.exists():
        return {}
        
    try:
        with open(rules_path, "r", encoding="utf-8") as f:
            rules = yaml.safe_load(f) or {}
            validate_rules(rules)
            return rules
    except Exception as e:
        logger.error(f"Failed to load rules: {e}")
        return {}

def load_rules() -> Dict[str, Any]:
    """Load and optionally validate rules.yaml."""
    rules_path = get_rules_path()
    return _load_rules_cached(str(rules_path.absolute()))

def get_protected_branches() -> list[str]:
    """Get protected branches, providing safe defaults."""
    rules = load_rules()
    try:
        return rules["git"]["safety"]["protected_branches"]
    except KeyError:
        return ["main", "master", "production", "prod"]

def get_commit_allowed_types() -> list[str]:
    rules = load_rules()
    try:
        return rules["git"]["commit"]["allowed_types"]
    except KeyError:
        return []

def get_commit_message_regex() -> str:
    rules = load_rules()
    try:
        return rules["git"]["commit"]["message_regex"]
    except KeyError:
        return ""

def get_commit_subject_max_length() -> int:
    rules = load_rules()
    try:
        return rules["git"]["commit"]["subject_max_length"]
    except KeyError:
        return 72

def get_commit_body_wrap_length() -> int:
    rules = load_rules()
    try:
        return rules["git"]["commit"]["body_wrap_length"]
    except KeyError:
        return 80

def get_commit_grouping_signals() -> list[str]:
    rules = load_rules()
    try:
        return rules["git"]["commit"]["grouping_signals"]
    except KeyError:
        return []

def get_commit_max_groups() -> int:
    """Max number of commit groups allowed in a single plan."""
    rules = load_rules()
    try:
        return rules["git"]["commit"]["max_groups"]
    except KeyError:
        return 8

def get_diff_max_lines_per_file() -> int:
    """Max diff lines allowed per individual file."""
    rules = load_rules()
    try:
        return rules["git"]["diff"]["max_diff_lines_per_file"]
    except KeyError:
        return 500

def get_diff_max_total_lines() -> int:
    rules = load_rules()
    try:
        return rules["git"]["diff"]["max_total_diff_lines"]
    except KeyError:
        return 3000

def get_allow_direct_actions_to_protected() -> bool:
    """Check if direct actions to protected branches are permitted."""
    rules = load_rules()
    try:
        # Fallback to False if safety context exists but key doesn't
        return rules["git"]["safety"].get("allow_direct_actions_to_protected", False)
    except KeyError:
        return False

def get_release_version_files() -> list[str]:
    """Get the list of files to update for a release."""
    rules = load_rules()
    try:
        return rules["git"]["release"]["version_files"]
    except KeyError:
        return []

def get_release_tag_regex() -> str:
    """Get the regex pattern for validating release tags."""
    rules = load_rules()
    try:
        return rules["git"]["release"]["tag_regex"]
    except KeyError:
        return "^v\\d+\\.\\d+\\.\\d+$"  # Default to v1.2.3 format

def get_full_commit_rules() -> Dict[str, Any]:
    """Returns the comprehensive set of commit rules for AI consumption."""
    return {
        "allowed_types": get_commit_allowed_types(),
        "message_regex": get_commit_message_regex(),
        "subject_max_length": get_commit_subject_max_length(),
        "body_wrap_length": get_commit_body_wrap_length(),
        "grouping_signals": get_commit_grouping_signals(),
        "max_groups": get_commit_max_groups(),
    }
