from pathlib import Path
from typing import Any

import yaml

from .catalog import get_default_tool, get_default_vault, get_vault_by_name_or_path
from .tools import get_tool, get_supported_tools

# Directory-based artifact types (create a directory with a main file inside)
_DIRECTORY_TYPES = {"skill"}
# File-based artifact types (create a single .md file)
_FILE_TYPES = {"command", "agent"}

# Mapping of artifact type to subdirectory name in vaults
_TYPE_SUBDIRS = {
    "skill": "skills",
    "command": "commands",
    "agent": "agents",
}


def create_artifact(
    artifact_type: str,
    name: str,
    description: str | None = None,
    content: str | None = None,
    extra_fields: dict[str, str] | None = None,
    target_path: Path | None = None,
) -> dict[str, Any]:
    """Create a new artifact with YAML frontmatter.

    Skills are directory-based (creates <target>/SKILL.md).
    Commands and agents are file-based (creates <target>.md or uses target_path directly).

    For commands, no 'name' field is added to frontmatter (filename IS the name).
    For agents and skills, 'name' is added to frontmatter.

    Args:
        artifact_type: One of "skill", "command", "agent".
        name: The artifact identifier.
        description: Description for frontmatter.
        content: Markdown body content after frontmatter.
        extra_fields: Additional key-value pairs for frontmatter.
        target_path: Full path to the artifact target.
            - For skills: the skill directory (e.g., vault/skills/my-skill/)
            - For commands/agents: the .md file path (e.g., vault/commands/my-cmd.md)

    Returns:
        Result dict with keys:
            - success: Whether the artifact was created
            - path: Path to the created file (if successful)
            - error: Error message (if failed)
    """
    if target_path is None:
        return {"success": False, "path": None, "error": "No target path provided"}

    if artifact_type in _DIRECTORY_TYPES:
        # Directory-based (skills)
        artifact_dir = target_path
        artifact_file = artifact_dir / "SKILL.md"

        if artifact_dir.exists():
            return {
                "success": False,
                "path": None,
                "error": f"Skill '{artifact_dir.name}' already exists at {artifact_dir}",
            }

        # Build frontmatter — skills include name
        frontmatter: dict[str, Any] = {"name": name}
        if description:
            frontmatter["description"] = description
        if extra_fields:
            frontmatter.update(extra_fields)

        yaml_str = yaml.dump(frontmatter, default_flow_style=False, sort_keys=False)
        file_content = f"---\n{yaml_str}---\n"
        if content:
            file_content += content + "\n"

        artifact_dir.mkdir(parents=True, exist_ok=True)
        artifact_file.write_text(file_content)

        return {"success": True, "path": str(artifact_file), "error": None}
    else:
        # File-based (commands, agents)
        artifact_file = target_path

        if artifact_file.exists():
            return {
                "success": False,
                "path": None,
                "error": f"{artifact_type.title()} '{artifact_file.stem}' already exists at {artifact_file}",
            }

        # Build frontmatter
        frontmatter = {}
        # Agents include name in frontmatter when explicitly provided; commands do not
        if artifact_type == "agent" and name is not None:
            frontmatter["name"] = name
        if description:
            frontmatter["description"] = description
        if extra_fields:
            frontmatter.update(extra_fields)

        yaml_str = yaml.dump(frontmatter, default_flow_style=False, sort_keys=False)
        file_content = f"---\n{yaml_str}---\n"
        if content:
            file_content += content + "\n"

        artifact_file.parent.mkdir(parents=True, exist_ok=True)
        artifact_file.write_text(file_content)

        return {"success": True, "path": str(artifact_file), "error": None}


# Backward-compatible alias
def create_skill(
    name: str,
    description: str | None = None,
    content: str | None = None,
    extra_fields: dict[str, str] | None = None,
    target_path: Path | None = None,
) -> dict[str, Any]:
    """Create a new skill. Delegates to create_artifact."""
    return create_artifact(
        artifact_type="skill",
        name=name,
        description=description,
        content=content,
        extra_fields=extra_fields,
        target_path=target_path,
    )


def resolve_vault_target(
    artifact_name: str,
    artifact_type: str = "skill",
    vault: str | None = None,
) -> dict[str, Any]:
    """Resolve the target path for vault-based artifact creation.

    Args:
        artifact_name: The artifact identifier.
        artifact_type: One of "skill", "command", "agent".
        vault: Optional vault name or path. Uses default vault if None.

    Returns:
        Result dict with keys:
            - success: Whether resolution succeeded
            - path: The resolved target path (if successful)
            - error: Error message (if failed)
    """
    if vault:
        vault_path = get_vault_by_name_or_path(vault)
        if vault_path is None:
            return {
                "success": False,
                "path": None,
                "error": f"Vault not found: {vault}",
            }
    else:
        vault_path = get_default_vault()
        if vault_path is None:
            return {
                "success": False,
                "path": None,
                "error": "No default vault set. Use 'art vault add' or 'art vault init' to set up a vault.",
            }

    subdir = _TYPE_SUBDIRS[artifact_type]
    if artifact_type in _DIRECTORY_TYPES:
        target = Path(vault_path) / subdir / artifact_name
    else:
        target = Path(vault_path) / subdir / f"{artifact_name}.md"

    return {"success": True, "path": target, "error": None}


def resolve_project_target(
    artifact_name: str,
    artifact_type: str = "skill",
    tools: list[str] | None = None,
) -> dict[str, Any]:
    """Resolve target paths for project-local artifact creation.

    Args:
        artifact_name: The artifact identifier.
        artifact_type: One of "skill", "command", "agent".
        tools: List of tool names. Uses all supported tools if None.

    Returns:
        Result dict with keys:
            - success: Whether resolution succeeded
            - paths: List of resolved target paths (if successful)
            - error: Error message (if failed)
    """
    if tools is None:
        supported = get_supported_tools()
        if not supported:
            return {
                "success": False,
                "paths": [],
                "error": "No supported tools found",
            }
        tools = supported

    subdir = _TYPE_SUBDIRS[artifact_type]
    paths = []
    for tool_name in tools:
        adapter = get_tool(tool_name)
        if adapter is None:
            return {
                "success": False,
                "paths": [],
                "error": f"Unknown tool: {tool_name}",
            }
        if subdir not in adapter.supported_types:
            return {
                "success": False,
                "paths": [],
                "error": f"Tool '{tool_name}' does not support {artifact_type}s",
            }
        dest = adapter.get_destination(subdir, Path.cwd())
        if artifact_type in _DIRECTORY_TYPES:
            target = dest / artifact_name
        else:
            target = dest / f"{artifact_name}.md"
        paths.append(target)

    return {"success": True, "paths": paths, "error": None}


def _parse_frontmatter_name(file_path: Path) -> str | None:
    """Read YAML frontmatter from a file and extract the 'name' field.

    Only reads lines until the closing '---' delimiter.
    """
    try:
        with file_path.open(encoding="utf-8") as f:
            first_line = f.readline().strip()
            if first_line != "---":
                return None
            for line in f:
                stripped = line.strip()
                if stripped == "---":
                    break
                if stripped.startswith("name:"):
                    # Extract value after "name:"
                    value = stripped[5:].strip()
                    # Remove surrounding quotes if present
                    if (value.startswith('"') and value.endswith('"')) or \
                       (value.startswith("'") and value.endswith("'")):
                        value = value[1:-1]
                    return value if value else None
    except (OSError, UnicodeDecodeError):
        pass
    return None


def _find_by_frontmatter_name(
    artifact_type: str,
    name: str,
    search_dir: Path,
) -> Path | None:
    """Scan artifacts of a type and return the first whose frontmatter 'name' matches.

    Searches in alphabetical filesystem order.
    """
    subdir = _TYPE_SUBDIRS.get(artifact_type)
    if subdir is None:
        return None

    base = search_dir / subdir
    if not base.is_dir():
        return None

    if artifact_type in _DIRECTORY_TYPES:
        # Skills: scan subdirectories for SKILL.md
        for item in sorted(base.iterdir()):
            if item.is_dir():
                skill_file = item / "SKILL.md"
                if skill_file.is_file():
                    fm_name = _parse_frontmatter_name(skill_file)
                    if fm_name == name:
                        return skill_file
    else:
        # Commands/agents: scan .md files
        for item in sorted(base.iterdir()):
            if item.is_file() and item.suffix == ".md":
                fm_name = _parse_frontmatter_name(item)
                if fm_name == name:
                    return item

    return None


def find_artifact_in_vault(
    name: str,
    vault_path: str,
    artifact_type: str | None = None,
) -> list[dict[str, Any]]:
    """Find artifacts matching name (or frontmatter name) in a vault.

    Args:
        name: Artifact name to search for.
        vault_path: Path to vault directory.
        artifact_type: If set, only search that type. Otherwise search all types.

    Returns:
        List of match dicts: {"type": str, "path": Path, "dir": Path}
        - For skills: "path" is SKILL.md, "dir" is the skill directory.
        - For commands/agents: "path" is the .md file, "dir" is its parent.
    """
    types_to_search = [artifact_type] if artifact_type else ["skill", "command", "agent"]
    matches: list[dict[str, Any]] = []
    base = Path(vault_path)

    for atype in types_to_search:
        subdir = _TYPE_SUBDIRS.get(atype)
        if subdir is None:
            continue
        type_dir = base / subdir
        if not type_dir.is_dir():
            continue

        if atype in _DIRECTORY_TYPES:
            candidate = type_dir / name / "SKILL.md"
            if candidate.exists():
                matches.append({"type": atype, "path": candidate, "dir": candidate.parent})
            else:
                # Frontmatter fallback
                fm_match = _find_by_frontmatter_name(atype, name, base)
                if fm_match is not None:
                    matches.append({"type": atype, "path": fm_match, "dir": fm_match.parent})
        else:
            candidate = type_dir / f"{name}.md"
            if candidate.exists():
                matches.append({"type": atype, "path": candidate, "dir": candidate.parent})
            else:
                fm_match = _find_by_frontmatter_name(atype, name, base)
                if fm_match is not None:
                    matches.append({"type": atype, "path": fm_match, "dir": fm_match.parent})

    return matches


def resolve_edit_target(
    artifact_type: str,
    artifact_name: str,
    vault: str | None = None,
    here: bool = False,
    tools: list[str] | None = None,
) -> dict[str, Any]:
    """Resolve the path to an artifact's main file for editing.

    Args:
        artifact_type: One of "skill", "command", "agent".
        artifact_name: The artifact identifier.
        vault: Optional vault name or path (vault mode).
        here: If True, resolve in current project instead of vault.
        tools: Tool names for --here mode.

    Returns:
        Result dict with keys:
            - success: Whether resolution succeeded
            - path: The resolved file path (if successful)
            - error: Error message (if failed)
    """
    subdir = _TYPE_SUBDIRS.get(artifact_type)
    if subdir is None:
        return {"success": False, "path": None, "error": f"Invalid artifact type: {artifact_type}"}

    if here:
        # Project-local mode
        if tools is None:
            default_tool = get_default_tool()
            tools = [default_tool]

        for tool_name in tools:
            adapter = get_tool(tool_name)
            if adapter is None:
                return {"success": False, "path": None, "error": f"Unknown tool: {tool_name}"}

            if subdir not in adapter.supported_types:
                continue

            dest = adapter.get_destination(subdir, Path.cwd())
            if artifact_type in _DIRECTORY_TYPES:
                target = dest / artifact_name / "SKILL.md"
            else:
                target = dest / f"{artifact_name}.md"

            if target.exists():
                return {"success": True, "path": target, "error": None}

        # Frontmatter fallback for --here mode
        for tool_name in tools:
            adapter = get_tool(tool_name)
            if adapter is None:
                continue
            if subdir not in adapter.supported_types:
                continue
            dest = adapter.get_destination(subdir, Path.cwd())
            fm_match = _find_by_frontmatter_name(artifact_type, artifact_name, dest.parent)
            if fm_match is not None:
                return {"success": True, "path": fm_match, "error": None}

        return {
            "success": False,
            "path": None,
            "error": f"{artifact_type.title()} '{artifact_name}' not found in project",
        }
    else:
        # Vault mode
        if vault:
            vault_path = get_vault_by_name_or_path(vault)
            if vault_path is None:
                return {"success": False, "path": None, "error": f"Vault not found: {vault}"}
        else:
            vault_path = get_default_vault()
            if vault_path is None:
                return {
                    "success": False,
                    "path": None,
                    "error": "No default vault set. Use 'art vault add' or 'art vault init' to set up a vault.",
                }

        if artifact_type in _DIRECTORY_TYPES:
            target = Path(vault_path) / subdir / artifact_name / "SKILL.md"
        else:
            target = Path(vault_path) / subdir / f"{artifact_name}.md"

        if not target.exists():
            # Frontmatter fallback
            fm_match = _find_by_frontmatter_name(artifact_type, artifact_name, Path(vault_path))
            if fm_match is not None:
                return {"success": True, "path": fm_match, "error": None}
            return {
                "success": False,
                "path": None,
                "error": f"{artifact_type.title()} '{artifact_name}' not found in vault",
            }

        return {"success": True, "path": target, "error": None}
