import re
import json
from pathlib import Path

from mixersystem.data.repository import (
    context_scope,
    get_context,
    load_settings,
    parse_result,
    parse_task,
    update_frontmatter,
)
from mixersystem.workflows.task.agents import router
from mixersystem.data import linear_sync


def _is_linear_task_id(task_id: str, task_prefix: str) -> bool:
    return task_id.upper().startswith(f"{task_prefix.upper()}-")


def _normalize_prefix(value: str | None) -> str | None:
    if not isinstance(value, str):
        return None
    stripped = value.strip()
    return stripped or None


def _settings_linear_prefix() -> str | None:
    try:
        settings = load_settings()
    except FileNotFoundError:
        return None
    linear = settings.get("linear")

    candidates: list[str | None] = []
    if isinstance(linear, dict):
        candidates.extend(
            [
                linear.get("team_prefix"),
                linear.get("prefix"),
            ]
        )
    candidates.append(settings.get("linear_team_prefix"))

    for candidate in candidates:
        prefix = _normalize_prefix(candidate)
        if prefix:
            return prefix
    return None


def _resolve_linear_prefix(task_prefix: str | None) -> str | None:
    configured_prefix = _settings_linear_prefix()
    provided_prefix = _normalize_prefix(task_prefix)

    # Linear routing is allowed only when both values exist and match.
    if not configured_prefix or not provided_prefix:
        return None
    if provided_prefix.upper() != configured_prefix.upper():
        return None
    return configured_prefix


def _normalize_modules(modules: list[str] | str | None) -> list[str] | None:
    if modules is None:
        return None
    if isinstance(modules, str):
        stripped = modules.strip()
        if stripped.startswith("[") and stripped.endswith("]"):
            try:
                parsed = json.loads(stripped)
                if isinstance(parsed, list):
                    modules = parsed
                else:
                    modules = [m.strip() for m in stripped.split(",") if m.strip()]
            except json.JSONDecodeError:
                modules = [m.strip() for m in stripped.split(",") if m.strip()]
        else:
            modules = [m.strip() for m in stripped.split(",") if m.strip()]
    if not isinstance(modules, list):
        raise TypeError("modules must be a list[str], comma-separated string, or None")
    cleaned = []
    seen = set()
    for m in modules:
        item = str(m).strip()
        if not item or item in seen:
            continue
        seen.add(item)
        cleaned.append(item)
    if "all" in cleaned and len(cleaned) > 1:
        raise ValueError("modules cannot mix 'all' with specific module names")
    return cleaned or None


def _title_to_slug(title: str) -> str:
    words = re.sub(r"[^a-zA-Z0-9\s]", "", title).lower().split()
    slug = "-".join(words[:4])
    return slug or "new-task"


def _write_local_task(*, task_path: str, task_id: str, title: str,
                      description: str, modules: list[str] | None) -> None:
    body = f"# {title}\n\n## Description\n\n{description.strip()}\n"
    p = Path(task_path)
    p.parent.mkdir(parents=True, exist_ok=True)
    p.write_text(body)

    update_frontmatter(
        task_path,
        id=task_id,
        name=_title_to_slug(title),
        modules=modules or ["all"],
    )


async def _ensure_modules(*, task_path: str, modules: list[str] | None) -> None:
    if modules is not None:
        update_frontmatter(task_path, modules=modules)
        parse_task(task_path)
        return

    task_data = parse_task(task_path)
    if task_data.modules != ["all"]:
        return

    router_response = await router.run()
    result = parse_result(router_response)
    raw = result.get("modules", "all")
    routed = _normalize_modules(raw)
    update_frontmatter(task_path, modules=routed or ["all"])
    parse_task(task_path)


def _rename_work_folder(*, work_folder: str, new_folder_name: str) -> None:
    source = Path(work_folder).resolve()
    target = source.parent / new_folder_name

    if source.name == new_folder_name:
        return
    if target.exists():
        raise FileExistsError(f"Cannot rename work folder: target already exists: {target}")
    source.rename(target)


async def run(work_folder: str, task_id: str, task_prefix: str | None = None,
              size: str = "M", depth: int | None = None,
              description: str | None = None, title: str | None = None,
              modules: list[str] | str | None = None,
              sync: bool | None = None,
              sync_to_linear: bool | None = None,
              api_key: str | None = None, team_id: str | None = None) -> None:
    """Create/update task.md from Linear or local args, optionally sync back to Linear."""
    if not task_id:
        raise ValueError("task_id is required")
    if sync is not None and sync_to_linear is not None and sync != sync_to_linear:
        raise ValueError("sync and sync_to_linear cannot conflict")

    normalized_modules = _normalize_modules(modules)
    should_sync = sync if sync is not None else bool(sync_to_linear)

    with context_scope(
        work_folder=str(Path(work_folder).resolve()),
        size=size,
        depth=depth,
    ):
        effective_prefix = _resolve_linear_prefix(task_prefix)
        is_linear = bool(effective_prefix and _is_linear_task_id(task_id, effective_prefix))
        ctx = get_context()
        task_path = ctx.paths["task_path"]
        has_existing_task = Path(task_path).is_file()

        if not is_linear and not description and not has_existing_task:
            raise ValueError(
                "description is required for local tasks. "
                "Linear task ids must match the configured team prefix in .mixer/settings.json."
            )

        if is_linear:
            linear_sync.load(issue_id=task_id, task_path=task_path, api_key=api_key)
            update_frontmatter(task_path, id=task_id)
        elif has_existing_task and description is None:
            update_frontmatter(task_path, id=task_id)
            existing_task = parse_task(task_path)
            if not existing_task.name:
                inferred_title = existing_task.title or title or "New Task"
                update_frontmatter(task_path, name=_title_to_slug(inferred_title))
        else:
            local_title = (title or "New Task").strip() or "New Task"
            _write_local_task(
                task_path=task_path,
                task_id=task_id,
                title=local_title,
                description=description or "",
                modules=normalized_modules,
            )

        await _ensure_modules(task_path=task_path, modules=normalized_modules)

        if not should_sync:
            return

        if is_linear:
            linear_sync.update(issue_id=task_id, task_path=task_path, api_key=api_key)
            return

        new_issue_id = linear_sync.push(task_path=task_path, api_key=api_key, team_id=team_id)
        update_frontmatter(task_path, id=new_issue_id)
        _rename_work_folder(work_folder=ctx.work_folder, new_folder_name=new_issue_id)


if __name__ == "__main__":
    from mixersystem.data.repository import run_cli
    run_cli(run)
