"""Parse NL local file-search commands into structured intent and execution plans."""

from __future__ import annotations

import re
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Literal

from .capability_resolver import FolderScope, resolve_folder_scope, resolve_user_folder

LocalSearchTargetType = Literal["file", "folder", "any"]
LocalSearchAction = Literal["show_list", "open_folder", "open_file", "sort", "count", "report"]
SortField = Literal["name", "size", "date"]
SortOrder = Literal["asc", "desc"]

_SEARCH_VERBS_RE = re.compile(
    r"\b(?:найди|найти|покажи|показать|отсортируй|отсортир|поиск|ищи|search|find|show|list|sort)\b",
    re.IGNORECASE,
)
_TARGET_TYPE_PATTERNS: tuple[tuple[re.Pattern[str], LocalSearchTargetType], ...] = (
    (re.compile(r"\bpdf\b|\.pdf\b", re.IGNORECASE), "file"),
    (re.compile(r"\b(?:изображен|картин|image|photo|picture)\b", re.IGNORECASE), "file"),
    (re.compile(r"\b(?:видео|video)\b", re.IGNORECASE), "file"),
    (re.compile(r"\b(?:аудио|audio|music|музык)\b", re.IGNORECASE), "file"),
    (re.compile(r"\b(?:архив|archive|zip)\b", re.IGNORECASE), "file"),
    (re.compile(r"\b(?:документ|document|docx?)\b", re.IGNORECASE), "file"),
    (re.compile(r"\b(?:папк|folder|директор)\b", re.IGNORECASE), "folder"),
    (re.compile(r"\b(?:файл|file)\b", re.IGNORECASE), "file"),
)
_EXTENSION_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
    (re.compile(r"\bpdf\b|\.pdf\b", re.IGNORECASE), "pdf"),
    (re.compile(r"\b(?:png|jpg|jpeg|gif|webp|svg)\b", re.IGNORECASE), "png"),
    (re.compile(r"\b(?:docx?|word)\b", re.IGNORECASE), "doc"),
    (re.compile(r"\b(?:xlsx?|excel)\b", re.IGNORECASE), "xls"),
    (re.compile(r"\b(?:zip|rar|7z|tar|gz)\b", re.IGNORECASE), "zip"),
    (re.compile(r"\b(?:mp4|mkv|avi|mov)\b", re.IGNORECASE), "mp4"),
    (re.compile(r"\b(?:mp3|flac|wav|ogg)\b", re.IGNORECASE), "mp3"),
)
_SORT_BY_PATTERNS: tuple[tuple[re.Pattern[str], SortField], ...] = (
    (re.compile(r"\b(?:размер\w*|size)\b", re.IGNORECASE), "size"),
    (re.compile(r"\b(?:дат\w*|date|времени|mtime|modified)\b", re.IGNORECASE), "date"),
    (re.compile(r"\b(?:имен\w*|name|назван\w*)\b", re.IGNORECASE), "name"),
)
_SORT_ORDER_DESC_RE = re.compile(
    r"\b(?:убыван|desc|descending|больш(?:их|ие)|нов(?:ые|ых)|latest)\b",
    re.IGNORECASE,
)
_SORT_ORDER_ASC_RE = re.compile(
    r"\b(?:возрастан|asc|ascending|меньш(?:их|ие)|стары(?:е|х)|oldest)\b",
    re.IGNORECASE,
)
_NAME_CONTAINS_RE = re.compile(
    r"(?:содерж(?:ит|ащ)|named?|назван(?:ием|ие|ия)|именем)\s+[«\"']([^»\"']+)[»\"']",
    re.IGNORECASE,
)
_EXPLICIT_PATH_RE = re.compile(
    r"(?:^|[\s«\"'])(~/[^\s»\"']+|/[^\s»\"']+)",
)
_SIZE_GT_RE = re.compile(
    r"(?:больше|more than|>\s*)(\d+(?:[.,]\d+)?)\s*(kb|mb|gb|кб|мб|гб)?",
    re.IGNORECASE,
)
_SIZE_LT_RE = re.compile(
    r"(?:меньше|less than|<\s*)(\d+(?:[.,]\d+)?)\s*(kb|mb|gb|кб|мб|гб)?",
    re.IGNORECASE,
)
_DATE_TODAY_RE = re.compile(r"\b(?:сегодня|today)\b", re.IGNORECASE)
_DATE_YESTERDAY_RE = re.compile(r"\b(?:вчера|yesterday)\b", re.IGNORECASE)
_DATE_WEEK_RE = re.compile(r"\b(?:недел[юя]|week|recent|недавн)\b", re.IGNORECASE)
_COUNT_ACTION_RE = re.compile(r"\b(?:сколько|count|количество)\b", re.IGNORECASE)
_OPEN_FOLDER_RE = re.compile(r"\b(?:открой папк|open folder)\b", re.IGNORECASE)


@dataclass(frozen=True)
class LocalSearchIntent:
    intent: Literal["LOCAL_SEARCH"] = "LOCAL_SEARCH"
    target_type: LocalSearchTargetType = "file"
    extension: str = ""
    location: FolderScope | str = "unknown"
    location_path: str = ""
    sort_by: SortField = "name"
    sort_order: SortOrder = "desc"
    action: LocalSearchAction = "show_list"
    name_contains: str = ""
    min_size_bytes: int = 0
    max_size_bytes: int = 0
    modified_after_ts: float = 0.0
    policy_hint: str = ""
    user_term: str = ""

    def as_dict(self) -> dict[str, Any]:
        return {
            "intent": self.intent,
            "target_type": self.target_type,
            "extension": self.extension,
            "location": self.location,
            "location_path": self.location_path,
            "sort_by": self.sort_by,
            "sort_order": self.sort_order,
            "action": self.action,
            "name_contains": self.name_contains,
            "min_size_bytes": self.min_size_bytes,
            "max_size_bytes": self.max_size_bytes,
            "modified_after_ts": self.modified_after_ts,
            "policy_hint": self.policy_hint,
            "user_term": self.user_term,
        }


def _parse_size_bytes(value: str, unit: str) -> int:
    amount = float(str(value or "0").replace(",", "."))
    unit_lower = str(unit or "b").strip().lower()
    multipliers = {
        "b": 1,
        "kb": 1024,
        "кб": 1024,
        "mb": 1024 * 1024,
        "мб": 1024 * 1024,
        "gb": 1024 * 1024 * 1024,
        "гб": 1024 * 1024 * 1024,
    }
    return int(amount * multipliers.get(unit_lower, 1))


def _parse_extension(text: str) -> str:
    for pattern, ext in _EXTENSION_PATTERNS:
        if pattern.search(text):
            return ext
    return ""


def _parse_target_type(text: str) -> LocalSearchTargetType:
    for pattern, target in _TARGET_TYPE_PATTERNS:
        if pattern.search(text):
            return target
    return "file"


def _parse_sort(text: str) -> tuple[SortField, SortOrder]:
    sort_by: SortField = "name"
    for pattern, field in _SORT_BY_PATTERNS:
        if pattern.search(text):
            sort_by = field
            break
    if _SORT_ORDER_ASC_RE.search(text):
        return sort_by, "asc"
    if _SORT_ORDER_DESC_RE.search(text):
        return sort_by, "desc"
    if sort_by in {"size", "date"}:
        return sort_by, "desc"
    return sort_by, "asc"


def _parse_modified_after(text: str) -> float:
    now = datetime.now(timezone.utc)
    if _DATE_TODAY_RE.search(text):
        start = now.replace(hour=0, minute=0, second=0, microsecond=0)
        return start.timestamp()
    if _DATE_YESTERDAY_RE.search(text):
        start = (now - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
        return start.timestamp()
    if _DATE_WEEK_RE.search(text):
        return (now - timedelta(days=7)).timestamp()
    return 0.0


def _parse_action(text: str) -> LocalSearchAction:
    if _OPEN_FOLDER_RE.search(text):
        return "open_folder"
    if _COUNT_ACTION_RE.search(text):
        return "count"
    return "show_list"


def _resolve_location(text: str) -> tuple[FolderScope | str, str, str, str]:
    path_match = _EXPLICIT_PATH_RE.search(text)
    if path_match:
        raw = str(path_match.group(1) or "").strip()
        return "explicit", str(Path(raw).expanduser()), "", raw

    scope = resolve_folder_scope(text)
    if scope == "unknown":
        return "unknown", "", "", ""

    resolved = resolve_user_folder(text, scope=scope, for_read=True)
    if resolved is None:
        return scope, "", "", ""
    return resolved.scope, str(resolved.path), resolved.policy_hint, resolved.user_term


def is_local_search_intent(text: str) -> bool:
    """True when NL asks to find/list/sort local files in a user folder."""
    raw = str(text or "").strip()
    if not raw:
        return False

    from ..ui.avatar_intent_router import is_workspace_file_creation_intent

    if is_workspace_file_creation_intent(raw):
        return False

    if not _SEARCH_VERBS_RE.search(raw):
        return False
    scope = resolve_folder_scope(raw)
    has_explicit = bool(_EXPLICIT_PATH_RE.search(raw))
    has_file_target = any(pattern.search(raw) for pattern, _ in _TARGET_TYPE_PATTERNS)
    if scope == "unknown" and not has_explicit:
        return False
    return has_file_target or scope in {"downloads", "documents", "pictures", "desktop", "home"}


def parse_local_search_intent(text: str) -> LocalSearchIntent | None:
    raw = str(text or "").strip()
    if not is_local_search_intent(raw):
        return None

    location, location_path, policy_hint, user_term = _resolve_location(raw)
    if location == "unknown" or not location_path:
        return None

    extension = _parse_extension(raw)
    sort_by, sort_order = _parse_sort(raw)
    action = _parse_action(raw)
    name_match = _NAME_CONTAINS_RE.search(raw)
    name_contains = str(name_match.group(1) or "").strip() if name_match else ""

    min_size = 0
    max_size = 0
    gt = _SIZE_GT_RE.search(raw)
    if gt:
        min_size = _parse_size_bytes(gt.group(1), str(gt.group(2) or "b"))
    lt = _SIZE_LT_RE.search(raw)
    if lt:
        max_size = _parse_size_bytes(lt.group(1), str(lt.group(2) or "b"))

    pattern = f"*.{extension}" if extension else "*"
    if extension and action == "show_list" and "открой" not in raw.lower():
        action = "show_list"

    return LocalSearchIntent(
        target_type=_parse_target_type(raw),
        extension=extension,
        location=location,
        location_path=location_path,
        sort_by=sort_by,
        sort_order=sort_order,
        action=action,
        name_contains=name_contains,
        min_size_bytes=min_size,
        max_size_bytes=max_size,
        modified_after_ts=_parse_modified_after(raw),
        policy_hint=policy_hint,
        user_term=user_term,
    )


_JOB_KEYWORD_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
    (re.compile(r"\bмюнхен\b", re.IGNORECASE), "мюнхен"),
    (re.compile(r"\bmünchen\b", re.IGNORECASE), "münchen"),
    (re.compile(r"\bmunich\b", re.IGNORECASE), "munich"),
    (re.compile(r"\bводител\w*\b", re.IGNORECASE), "водитель"),
    (re.compile(r"\bdriver\b", re.IGNORECASE), "driver"),
    (re.compile(r"\bработ\w*\b", re.IGNORECASE), "работа"),
    (re.compile(r"\bваканс\w*\b", re.IGNORECASE), "вакансия"),
    (re.compile(r"\bобъявлен\w*\b", re.IGNORECASE), "объявление"),
    (re.compile(r"\bjob\b", re.IGNORECASE), "job"),
)
_LOCAL_COMPUTER_SEARCH_RE = re.compile(
    r"\b(?:поиск\w*\s+на\s+компьютер\w*|search\s+on\s+(?:the\s+)?computer)\b",
    re.IGNORECASE,
)
_LOCAL_JOB_AD_RE = re.compile(
    r"\b(?:объявлен\w*|ваканс\w*|job\s+ad|jobs?|работ\w*|driver|водител\w*)\b",
    re.IGNORECASE,
)


def extract_local_search_keywords(text: str) -> list[str]:
    """Topic keywords for filename scan (job ads, city, role)."""
    raw = str(text or "")
    found: list[str] = []
    for pattern, keyword in _JOB_KEYWORD_PATTERNS:
        if pattern.search(raw) and keyword not in found:
            found.append(keyword)
    return found[:4]


def is_local_computer_search_intent(text: str) -> bool:
    """LOCAL_SEARCH on computer without explicit file type — keyword filename scan."""
    raw = str(text or "").strip()
    if not raw:
        return False
    if is_local_search_intent(raw):
        return False
    if _LOCAL_COMPUTER_SEARCH_RE.search(raw):
        return True
    if _LOCAL_JOB_AD_RE.search(raw) and re.search(
        r"\b(?:на\s+компьютер\w*|on\s+(?:this\s+)?computer|локальн\w*)\b",
        raw,
        re.I,
    ):
        return True
    return False


def build_local_computer_search_plan(text: str) -> dict[str, Any] | None:
    """Scan Downloads, Documents, Desktop for filenames matching topic keywords."""
    if not is_local_computer_search_intent(text):
        return None

    keywords = extract_local_search_keywords(text) or ["объявлен", "ваканс", "job", "работ"]
    scopes: list[FolderScope] = ["downloads", "documents", "desktop"]
    steps: list[dict[str, Any]] = []
    for index, scope in enumerate(scopes, start=1):
        resolved = resolve_user_folder(text, scope=scope, for_read=True)
        if resolved is None:
            continue
        payload: dict[str, Any] = {
            "absolute_path": str(resolved.path),
            "pattern": "*",
            "sort_by": "date",
            "sort_order": "desc",
            "action": "show_list",
            "user_folder_scope": scope,
            "policy_hint": resolved.policy_hint,
            "local_search_intent": {
                "intent": "LOCAL_SEARCH",
                "location": scope,
                "location_path": str(resolved.path),
                "name_contains": keywords[0] if keywords else "",
                "keywords": keywords,
                "computer_job_search": True,
            },
            "user_consent_state": "consented_via_connect_flow",
        }
        if keywords:
            payload["name_contains"] = keywords[0]
            payload["keywords_any"] = keywords
        steps.append(
            {
                "step_id": f"s{index}",
                "command_type": "list_files_in_folder",
                "payload": payload,
            }
        )

    if not steps:
        return None

    kw_label = ", ".join(keywords) if keywords else "объявления"
    return {
        "goal_summary": f"Локальный поиск на компьютере: {kw_label}",
        "empty_honest_message": (
            "Локальный поиск объявлений о работе не нашёл индексированных файлов — "
            "уточните папку или используйте интернет-поиск."
        ),
        "steps": steps,
    }


def build_local_search_plan(text: str) -> dict[str, Any] | None:
    """Build a single-step (or list+open) plan from parsed LOCAL_SEARCH intent."""
    intent = parse_local_search_intent(text)
    if intent is None:
        return None

    pattern = f"*.{intent.extension}" if intent.extension else "*"
    payload: dict[str, Any] = {
        "absolute_path": intent.location_path,
        "pattern": pattern,
        "sort_by": intent.sort_by,
        "sort_order": intent.sort_order,
        "action": intent.action,
        "user_folder_scope": str(intent.location),
        "policy_hint": intent.policy_hint,
        "local_search_intent": intent.as_dict(),
        "user_consent_state": "consented_via_connect_flow",
    }
    if intent.extension:
        payload["extension"] = intent.extension
    if intent.name_contains:
        payload["name_contains"] = intent.name_contains
    if intent.min_size_bytes > 0:
        payload["min_size_bytes"] = intent.min_size_bytes
    if intent.max_size_bytes > 0:
        payload["max_size_bytes"] = intent.max_size_bytes
    if intent.modified_after_ts > 0:
        payload["modified_after_ts"] = intent.modified_after_ts

    steps: list[dict[str, Any]] = [
        {
            "step_id": "s1",
            "command_type": "list_files_in_folder",
            "payload": payload,
        }
    ]
    if intent.action == "open_folder":
        steps.append(
            {
                "step_id": "s2",
                "command_type": "open_folder",
                "depends_on": ["s1"],
                "payload": {
                    "absolute_path": intent.location_path,
                    "user_folder_scope": str(intent.location),
                    "user_consent_state": "consented_via_connect_flow",
                },
            }
        )

    ext_label = intent.extension.upper() if intent.extension else "файлы"
    goal = f"Найти {ext_label} в {intent.location} ({intent.location_path})"
    plan: dict[str, Any] = {"goal_summary": goal, "steps": steps}
    if intent.policy_hint:
        plan["policy_hint"] = intent.policy_hint
    return plan
