from __future__ import annotations

import hashlib
import json
import re
from pathlib import Path

from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.steps.step5_execute_subprocesses.classifier import (
    DeleteIntentHeuristic,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.create_doc import (
    CreateDocSubprocess,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.delete_doc import (
    DeleteDocSubprocess,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.subprocesses.edit_doc import EditDocSubprocess
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.context import (
    DocUpdateFromFeatureV2Context,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature_v2.workflow_runtime.models import (
    AccumulatedPageDraft,
    DocAction,
    RequirementTaskContext,
)
from app.core.agent.utils.llm import AgentLlmService
from app.schemas.changeset import ChangeItem, ChangeOp


class TaskAttributeResolver:
    def __init__(self, llm: AgentLlmService) -> None:
        self._llm = llm

    def resolve(self, context: DocUpdateFromFeatureV2Context, task: RequirementTaskContext, rules_text: str) -> None:
        task.doc_type = str(task.metadata.get("doc_type") or task.metadata.get("type") or "").strip()
        task.doc_id = str(task.metadata.get("id") or self._slug(task.heading)).strip()
        task.application = str(task.metadata.get("application") or context.analytics_meta.application or "").strip()
        task.platform = str(task.metadata.get("platform") or context.analytics_meta.platform or "").strip().lower()
        task.domain = str(task.metadata.get("domain") or context.analytics_meta.domain or "").strip()
        task.subdomain = str(
            task.metadata.get("sub_domain") or task.metadata.get("subdomain") or context.analytics_meta.subdomain or ""
        ).strip()
        missing = [
            name
            for name, value in [
                ("doc_type", task.doc_type),
                ("id", task.doc_id),
                ("application", task.application),
                ("platform", task.platform),
                ("domain", task.domain),
                ("sub_domain", task.subdomain),
            ]
            if not value
        ]
        if not missing:
            return
        payload = {
            "missing": missing,
            "full_document": context.source_content,
            "requirement": {"heading": task.heading, "body": task.body, "metadata": task.metadata},
            "docs_catalog_paths": [str(row.get("path") or "") for row in context.docs_catalog_rows[:300]],
            "rules": rules_text,
        }
        raw = self._llm.generate(
            "v2_docs_update_v2.resolve_attributes_fallback",
            json.dumps(payload, ensure_ascii=False, indent=2),
            log_context="workflow.v2.docs_update.from_feature_v2.resolve_attributes",
        )
        parsed = self._json_or_empty(raw)
        task.doc_type = task.doc_type or str(parsed.get("doc_type") or parsed.get("type") or "").strip()
        task.doc_id = task.doc_id or str(parsed.get("id") or self._slug(task.heading)).strip()
        task.application = task.application or str(parsed.get("application") or "").strip()
        task.platform = task.platform or str(parsed.get("platform") or "web").strip().lower()
        task.domain = task.domain or str(parsed.get("domain") or "").strip()
        task.subdomain = task.subdomain or str(parsed.get("sub_domain") or parsed.get("subdomain") or "").strip()

    def _slug(self, value: str) -> str:
        return re.sub(r"[^a-z0-9._-]+", "-", (value or "").strip().lower()).strip(".-") or "untitled"

    def _json_or_empty(self, raw: str) -> dict[str, object]:
        value = str(raw or "").strip()
        if not value:
            return {}
        try:
            payload = json.loads(value)
            return payload if isinstance(payload, dict) else {}
        except Exception:
            return {}


class TaskActionResolver:
    def __init__(self, llm: AgentLlmService) -> None:
        self._llm = llm
        self._delete_heuristic = DeleteIntentHeuristic()

    def resolve(self, task: RequirementTaskContext, known_paths: set[str]) -> DocAction:
        explicit = self._explicit_action(task)
        if explicit is not None:
            return explicit
        if self._delete_heuristic.is_delete(task.body):
            return DocAction.DELETE
        return DocAction.EDIT if task.path in known_paths else DocAction.CREATE

    def _explicit_action(self, task: RequirementTaskContext) -> DocAction | None:
        value = str(task.metadata.get("action") or task.metadata.get("op") or task.metadata.get("operation") or "")
        normalized = value.strip().lower()
        if normalized in {"create", "add", "new"}:
            return DocAction.CREATE
        if normalized in {"update", "edit", "modify"}:
            return DocAction.EDIT
        if normalized in {"delete", "remove"}:
            return DocAction.DELETE
        return None

class TaskChangeExecutor:
    def __init__(self, llm: AgentLlmService) -> None:
        self._create = CreateDocSubprocess(llm)
        self._edit = EditDocSubprocess(llm)
        self._delete = DeleteDocSubprocess()

    def execute(self, context: DocUpdateFromFeatureV2Context, task: RequirementTaskContext) -> None:
        shared_context = self._build_shared_context(context)
        if task.action == DocAction.CREATE:
            result = self._create.run(task, rule_documents=context.rules, shared_context=shared_context)
            context.changeset.append(
                ChangeItem(op=ChangeOp.CREATE, path=task.path, proposed_content=result.content, reason=task.heading)
            )
            self._remember_change(context, task, result.content)
            return
        if task.action == DocAction.EDIT:
            current_content, base_hash = self._load_current_content(context, task.path)
            if not current_content or not base_hash:
                context.issues.append(f"Пропущен UPDATE для {task.path}: не найден текущий файл.")
                return
            result = self._edit.run(
                task,
                current_content=current_content,
                rule_documents=context.rules,
                shared_context=shared_context,
            )
            context.changeset.append(
                ChangeItem(
                    op=ChangeOp.UPDATE,
                    path=task.path,
                    base_hash=base_hash,
                    proposed_content=result.content,
                    reason=task.heading,
                )
            )
            self._remember_change(context, task, result.content)
            return
        _, base_hash = self._load_current_content(context, task.path)
        if not base_hash:
            context.issues.append(f"Пропущен DELETE для {task.path}: не найден текущий файл.")
            return
        self._delete.run(task)
        context.changeset.append(ChangeItem(op=ChangeOp.DELETE, path=task.path, base_hash=base_hash, reason=task.heading))
        self._remember_change(context, task, "")

    def _build_shared_context(self, context: DocUpdateFromFeatureV2Context) -> dict[str, object]:
        return {
            "analytics_meta": {
                "analysis_id": context.analytics_meta.analysis_id,
                "application": context.analytics_meta.application,
                "platform": context.analytics_meta.platform,
                "domain": context.analytics_meta.domain,
                "sub_domain": context.analytics_meta.subdomain,
            },
            "planned_pages": [
                {
                    "section_key": item.section_key,
                    "heading": item.heading,
                    "path": item.path,
                    "doc_id": item.doc_id,
                    "doc_type": item.doc_type,
                    "platform": item.platform,
                    "application": item.application,
                    "domain": item.domain,
                    "sub_domain": item.subdomain,
                    "action": item.action.value,
                }
                for item in context.requirement_tasks
            ],
            "accumulated_pages": [
                {
                    "path": item.path,
                    "heading": item.heading,
                    "doc_id": item.doc_id,
                    "doc_type": item.doc_type,
                    "platform": item.platform,
                    "action": item.action.value,
                    "content": item.content,
                }
                for item in context.accumulated_pages
            ],
        }

    def _load_current_content(self, context: DocUpdateFromFeatureV2Context, rel_path: str) -> tuple[str, str]:
        for item in reversed(context.accumulated_pages):
            if item.path == rel_path and item.action != DocAction.DELETE:
                content = item.content
                return content, hashlib.sha256(content.encode("utf-8")).hexdigest()
        root = Path(context.project_root or "")
        if not root.is_absolute():
            return "", ""
        target = root / rel_path
        if not target.exists() or not target.is_file():
            return "", ""
        content = target.read_text(encoding="utf-8")
        base_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()
        return content, base_hash

    def _remember_change(self, context: DocUpdateFromFeatureV2Context, task: RequirementTaskContext, content: str) -> None:
        context.accumulated_pages = [item for item in context.accumulated_pages if item.path != task.path]
        context.accumulated_pages.append(
            AccumulatedPageDraft(
                path=task.path,
                heading=task.heading,
                doc_id=task.doc_id,
                doc_type=task.doc_type,
                platform=task.platform,
                action=task.action,
                content=content,
            )
        )
