"""Configuration loading for Pulse.

Supports YAML (if PyYAML available) with JSON fallback.
"""

import json
import os
from typing import Any, Dict, List, Optional


# Minimal YAML subset parser for pulse.yaml configs.
# Handles the specific structure pulse needs: nested lists/dicts with string values.
# Falls back to JSON if the config file is .json.

def _parse_yaml_value(raw: str) -> Any:
    """Parse a scalar YAML value."""
    stripped = raw.strip()
    if stripped == "":
        return ""
    if stripped in ("true", "True", "yes", "on"):
        return True
    if stripped in ("false", "False", "no", "off"):
        return False
    if stripped in ("null", "~", ""):
        return None
    # Remove quotes
    if (stripped.startswith('"') and stripped.endswith('"')) or \
       (stripped.startswith("'") and stripped.endswith("'")):
        return stripped[1:-1]
    # Try int
    try:
        return int(stripped)
    except ValueError:
        pass
    # Try float
    try:
        return float(stripped)
    except ValueError:
        pass
    return stripped


def _get_indent(line: str) -> int:
    """Return number of leading spaces."""
    return len(line) - len(line.lstrip())


def _parse_yaml_lines(lines: List[str], start: int, base_indent: int) -> tuple:
    """Parse YAML lines starting at index `start` with `base_indent`.

    Returns (parsed_value, next_index).
    """
    if start >= len(lines):
        return None, start

    first_line = lines[start]
    stripped = first_line.strip()

    # List item
    if stripped.startswith("- "):
        result = []
        i = start
        while i < len(lines):
            line = lines[i]
            indent = _get_indent(line)
            s = line.strip()
            if indent < base_indent:
                break
            if indent == base_indent and not s.startswith("- "):
                break
            if indent == base_indent and s.startswith("- "):
                rest = s[2:].strip()
                if ":" in rest and not rest.startswith('"'):
                    # Inline key:value after dash — start of a mapping
                    item = {}
                    # Parse the first key:value
                    colon_pos = rest.index(":")
                    key = rest[:colon_pos].strip()
                    val_part = rest[colon_pos + 1:].strip()
                    if val_part:
                        item[key] = _parse_yaml_value(val_part)
                    else:
                        # Value on next lines
                        child_val, i = _parse_yaml_lines(lines, i + 1, base_indent + 2)
                        item[key] = child_val
                        # Continue parsing sibling keys at dash-child indent
                    i += 1 if val_part else 0
                    # Parse remaining keys at indent > base_indent
                    child_indent = base_indent + 2  # typical indent after "- "
                    while i < len(lines):
                        cl = lines[i]
                        ci = _get_indent(cl)
                        cs = cl.strip()
                        if ci < child_indent or cs == "" or (ci == base_indent and cs.startswith("- ")):
                            break
                        if ci >= child_indent and ":" in cs:
                            ccolon = cs.index(":")
                            ckey = cs[:ccolon].strip()
                            cval = cs[ccolon + 1:].strip()
                            if cval:
                                item[ckey] = _parse_yaml_value(cval)
                                i += 1
                            else:
                                child_val, i = _parse_yaml_lines(lines, i + 1, ci + 2)
                                item[ckey] = child_val
                        else:
                            break
                    result.append(item)
                else:
                    result.append(_parse_yaml_value(rest))
                    i += 1
            else:
                i += 1
        return result, i

    # Mapping
    if ":" in stripped:
        result = {}
        i = start
        while i < len(lines):
            line = lines[i]
            indent = _get_indent(line)
            s = line.strip()
            if s == "" or s.startswith("#"):
                i += 1
                continue
            if indent < base_indent:
                break
            if indent == base_indent and ":" in s:
                colon_pos = s.index(":")
                key = s[:colon_pos].strip()
                val_part = s[colon_pos + 1:].strip()
                if val_part:
                    result[key] = _parse_yaml_value(val_part)
                    i += 1
                else:
                    child_val, i = _parse_yaml_lines(lines, i + 1, base_indent + 2)
                    result[key] = child_val
            else:
                i += 1
        return result, i

    return _parse_yaml_value(stripped), start + 1


def parse_yaml(text: str) -> Dict[str, Any]:
    """Parse a simple YAML document. Supports the subset needed by pulse configs."""
    lines = []
    for line in text.split("\n"):
        # Skip comments and blank lines at top level for cleaning
        if line.strip().startswith("#") or line.strip() == "":
            continue
        lines.append(line)

    if not lines:
        return {}

    result, _ = _parse_yaml_lines(lines, 0, 0)
    if isinstance(result, dict):
        return result
    return {"_root": result}


def load_config(path: Optional[str] = None) -> Dict[str, Any]:
    """Load pulse configuration from file.

    Search order:
    1. Explicit path argument
    2. pulse.yaml in current directory
    3. pulse.json in current directory
    """
    candidates = []
    if path:
        candidates.append(path)
    else:
        candidates.append("pulse.yaml")
        candidates.append("pulse.yml")
        candidates.append("pulse.json")

    for candidate in candidates:
        if not os.path.exists(candidate):
            continue

        with open(candidate, "r", encoding="utf-8") as f:
            text = f.read()

        if candidate.endswith(".json"):
            return json.loads(text)

        # Try PyYAML first, fall back to built-in parser
        try:
            import yaml  # type: ignore
            return yaml.safe_load(text)
        except ImportError:
            return parse_yaml(text)

    raise FileNotFoundError(
        f"No pulse config found. Tried: {', '.join(candidates)}. "
        f"Run 'pulse init' to create one."
    )


def validate_config(config: Dict[str, Any]) -> List[str]:
    """Validate a pulse configuration. Returns list of error messages."""
    errors = []

    if "vitals" not in config:
        errors.append("Missing 'vitals' key in config")
        return errors

    vitals = config["vitals"]
    if not isinstance(vitals, list):
        errors.append("'vitals' must be a list")
        return errors

    valid_types = {"file_freshness", "json_key_changed", "log_entry", "process_output"}

    for i, vital in enumerate(vitals):
        if not isinstance(vital, dict):
            errors.append(f"vitals[{i}]: must be a mapping")
            continue
        if "name" not in vital:
            errors.append(f"vitals[{i}]: missing 'name'")
        if "checks" not in vital:
            errors.append(f"vitals[{i}]: missing 'checks'")
            continue

        checks = vital["checks"]
        if not isinstance(checks, list):
            errors.append(f"vitals[{i}]: 'checks' must be a list")
            continue

        for j, check in enumerate(checks):
            if not isinstance(check, dict):
                errors.append(f"vitals[{i}].checks[{j}]: must be a mapping")
                continue
            if "type" not in check:
                errors.append(f"vitals[{i}].checks[{j}]: missing 'type'")
            elif check["type"] not in valid_types:
                errors.append(
                    f"vitals[{i}].checks[{j}]: unknown type '{check['type']}'. "
                    f"Valid: {', '.join(sorted(valid_types))}"
                )

            ctype = check.get("type", "")
            if ctype in ("file_freshness", "json_key_changed", "log_entry"):
                if "path" not in check:
                    errors.append(f"vitals[{i}].checks[{j}]: missing 'path'")
            if ctype in ("file_freshness", "json_key_changed", "log_entry"):
                if "max_age" not in check:
                    errors.append(f"vitals[{i}].checks[{j}]: missing 'max_age'")
            if ctype == "json_key_changed" and "key" not in check:
                errors.append(f"vitals[{i}].checks[{j}]: missing 'key' for json_key_changed")
            if ctype == "log_entry" and "pattern" not in check:
                errors.append(f"vitals[{i}].checks[{j}]: missing 'pattern' for log_entry")
            if ctype == "process_output" and "command" not in check:
                errors.append(f"vitals[{i}].checks[{j}]: missing 'command' for process_output")

    return errors
