"""Одноразовый скрипт миграции открытых задач из v1 (tasks.json) в TickTick.

Запуск:
    # Сначала забрать tasks.json с Pi
    scp pi:~/tasks_bot_data/tasks.json /tmp/v1_tasks.json

    # Сухой прогон (показать что будет создано, ничего не делать)
    .venv/bin/python -m scripts.migrate_v1_to_ticktick \\
        --tasks-json /tmp/v1_tasks.json --dry-run

    # Реальная миграция
    .venv/bin/python -m scripts.migrate_v1_to_ticktick \\
        --tasks-json /tmp/v1_tasks.json

Логика:
- Открытые задачи (status НЕ в done/cancelled/migrated)
- v1 project → v2 brand (mars/channel/consulting/life остаются как есть;
  _inbox → системный TickTick Inbox)
- v1 deadline_kind: date/week + deadline → due, иначе None
- Маршрутизация в список через routing.resolve_list (без platform_hint,
  без strategic_flag — миграция нейтральная)
- POST task в TickTick через scripts.preflight http_client (sync, простой)
"""
import argparse
import json
import re
import sys
from datetime import date
from pathlib import Path

# Чтобы импорты src/tasks_bot работали
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from scripts.preflight import config  # noqa: E402
from scripts.preflight import http_client as http  # noqa: E402
from tasks_bot.ticktick.mapping import ProjectMapping  # noqa: E402
from tasks_bot.v2.routing import resolve_list  # noqa: E402


_PREFIX_RE = re.compile(r"^\s*(?:[-*•–—]|\d+[.)])\s+")


def normalize_text(raw: str) -> str:
    """Срез маркеров списка с начала строки."""
    return _PREFIX_RE.sub("", raw, count=1).strip()


def map_v1_task(
    task: dict,
    mapping: ProjectMapping,
) -> tuple[str | None, str, str, date | None, str]:
    """v1 task → (project_id, brand, list_name, due, content).

    project_id == None означает системный TickTick Inbox.
    """
    project = task.get("project", "_inbox")
    brand = "inbox" if project == "_inbox" else project

    deadline_kind = task.get("deadline_kind", "none")
    deadline = task.get("deadline")
    if deadline_kind != "none" and deadline:
        due = date.fromisoformat(deadline[:10])
    else:
        due = None

    content = normalize_text(task.get("text", ""))

    if brand == "inbox":
        return (None, "inbox", "Inbox", due, content)

    list_name = resolve_list(
        brand, due=due, platform_hint=None, strategic_flag=False,
    )
    project_id = mapping.id_for(brand, list_name)
    return (project_id, brand, list_name, due, content)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--tasks-json", required=True, help="Путь к tasks.json")
    parser.add_argument("--dry-run", action="store_true", help="Только показать, не создавать")
    parser.add_argument(
        "--mapping-json",
        default="scripts/preflight/projects_mapping.json",
    )
    parser.add_argument(
        "--skip",
        default="",
        help="Номера задач из dry-run для пропуска, через запятую (например: 1,5,7)",
    )
    args = parser.parse_args()

    cfg = config.load()
    token = cfg.access_token
    if not token:
        raise SystemExit("Нет TICKTICK_ACCESS_TOKEN в .env.preflight")

    mapping = ProjectMapping.load(Path(args.mapping_json))

    with open(args.tasks_json, encoding="utf-8") as f:
        data = json.load(f)
    tasks = data.get("tasks", data) if isinstance(data, dict) else data

    open_tasks = [
        t for t in tasks
        if (t.get("status") or "").lower() not in ("done", "cancelled", "migrated")
    ]
    skip_set: set[int] = set()
    if args.skip:
        skip_set = {int(x) for x in args.skip.split(",") if x.strip()}

    print(f"Открытых задач для миграции: {len(open_tasks)}")
    if skip_set:
        print(f"Пропускаем по --skip: {sorted(skip_set)}")
    print()

    created = 0
    failed = 0
    skipped = 0
    for i, t in enumerate(open_tasks, 1):
        project_id, brand, list_name, due, content = map_v1_task(t, mapping)
        due_str = due.isoformat() if due else "—"
        title_preview = content[:60] + ("…" if len(content) > 60 else "")

        if i in skip_set:
            print(
                f"  SKIP [{i:2d}/{len(open_tasks)}]  {brand:<11} / {list_name:<10}"
                f"  due={due_str}  {title_preview}"
            )
            skipped += 1
            continue

        marker = "DRY" if args.dry_run else "→  "
        print(
            f"  {marker} [{i:2d}/{len(open_tasks)}]  {brand:<11} / {list_name:<10}"
            f"  due={due_str}  {title_preview}"
        )

        if args.dry_run:
            continue

        body: dict = {"title": content}
        if project_id is not None:
            body["projectId"] = project_id
        if due:
            body["dueDate"] = f"{due.isoformat()}T00:00:00+05:00"
        try:
            http.post("task", token, body)
            created += 1
        except http.TickTickHTTPError as e:
            print(f"     !! FAILED: {e}")
            failed += 1

    print()
    if args.dry_run:
        will_create = len(open_tasks) - skipped
        print(f"DRY RUN — ничего не создано. Будет создано: {will_create}, пропущено: {skipped}.")
        print("Чтобы выполнить миграцию реально — повтори без --dry-run.")
    else:
        print(f"Создано: {created}, пропущено по --skip: {skipped}, не удалось: {failed}")


if __name__ == "__main__":
    main()
