from __future__ import annotations

import hashlib
from pathlib import Path

from app.core.agent.processes.v2.workflows.doc_update_from_feature.doc_rules_pipeline.changeset_generator import (
    DocRulesChangesetGenerator,
)
from app.core.agent.processes.v2.workflows.doc_update_from_feature.workflow_runtime.context import DocUpdateFromFeatureContext
from app.core.agent.utils.llm import AgentLlmService
from app.core.agent.utils.workflow import WorkflowStep
from app.schemas.changeset import ChangeItem
from app.schemas.changeset import ChangeOp
from app.schemas.changeset import PatchHunk


class BuildChangeInstructionsStep(WorkflowStep[DocUpdateFromFeatureContext]):
    step_id = "build_changeset"
    title = "Формирование changeset"

    def __init__(self, llm: AgentLlmService, doc_rules_enabled: bool = True) -> None:
        self._doc_rules_enabled = doc_rules_enabled
        self._generator = DocRulesChangesetGenerator(llm=llm)

    async def run(self, context: DocUpdateFromFeatureContext) -> DocUpdateFromFeatureContext:
        if context.answer or not context.planned_changes:
            return context
        llm_trace = context.runtime.trace.module("workflow.v2.docs_update.from_feature.llm")
        domain = (context.analytics_meta.domains or ["unknown"])[0]
        subdomain = (context.analytics_meta.subdomains or ["unknown"])[0]
        bundle = context.doc_rules_bundle
        use_doc_rules = self._doc_rules_enabled and context.doc_rules_enabled and bundle is not None
        for item in context.planned_changes:
            if use_doc_rules:
                assert bundle is not None
                payload, issue = self._generator.generate(
                    bundle=bundle,
                    item=item,
                    domain=domain,
                    subdomain=subdomain,
                    project_root=context.project_root,
                    trace=llm_trace,
                )
                if issue:
                    context.issues.append(issue)
                    continue
                if payload is not None:
                    context.changeset.append(payload)
                    continue
            payload = self._build_legacy_change(context, item, domain, subdomain)
            if payload is not None:
                context.changeset.append(payload)
        return context

    def trace_output(self, context: DocUpdateFromFeatureContext) -> dict[str, object]:
        return {"changeset_items": len(context.changeset), "issues": len(context.issues)}

    def _build_legacy_change(self, context: DocUpdateFromFeatureContext, item, domain: str, subdomain: str) -> ChangeItem | None:
        op = ChangeOp(item.op)
        if op == ChangeOp.DELETE:
            base_hash = self._resolve_base_hash(context, item.path)
            if not base_hash:
                context.issues.append(f"DELETE пропущен: не удалось вычислить base_hash для {item.path}.")
                return None
            return ChangeItem(
                op=op,
                path=item.path,
                base_hash=base_hash,
                reason=item.reason,
            )
        content = self._build_doc_content(item.doc_id, item.title, item.doc_type, domain, subdomain, item)
        if op == ChangeOp.CREATE:
            return ChangeItem(op=op, path=item.path, proposed_content=content, reason=item.reason)
        base_hash = self._resolve_base_hash(context, item.path)
        if not base_hash:
            context.issues.append(f"UPDATE пропущен: не удалось вычислить base_hash для {item.path}.")
            return None
        return ChangeItem(
            op=op,
            path=item.path,
            base_hash=base_hash,
            proposed_content=content,
            reason=item.reason,
            hunks=[PatchHunk(type="append_end", new_text="\n\n## Updated From System Analysis\n\nТребуется обновить содержимое по changeset.\n")],
        )

    def _build_doc_content(self, doc_id: str, title: str, doc_type: str, domain: str, subdomain: str, item) -> str:
        related = "\n".join(f"  - {ref}" for ref in item.related_docs) if item.related_docs else "  - TBD"
        source_refs = "\n".join(f"  - {ref}" for ref in item.source_refs) if item.source_refs else "  - TBD"
        functional_requirements = item.requirement_body.strip() or "Требования не были явно извлечены из unit."
        return (
            "---\n"
            f"id: {doc_id}\n"
            f"title: {title}\n"
            f"doc_type: {doc_type}\n"
            f"domain: {domain}\n"
            f"sub_domain: {subdomain}\n"
            "status: generated\n"
            "related_docs:\n"
            f"{related}\n"
            "source_of_truth: system_analysis\n"
            "system_analytics_refs:\n"
            f"{source_refs}\n"
            "---\n\n"
            "## Context\n\n"
            "Черновик сгенерирован workflow DOC_UPDATE/FROM_FEATURE на основе системной аналитики.\n\n"
            "## Functional Requirements\n\n"
            f"{functional_requirements}\n"
        )

    def _resolve_base_hash(self, context: DocUpdateFromFeatureContext, rel_path: str) -> str:
        root = Path(context.project_root or "").expanduser()
        if not root.is_absolute():
            return ""
        target = root / rel_path
        if not target.exists() or not target.is_file():
            return ""
        try:
            content = target.read_text(encoding="utf-8")
        except Exception:
            return ""
        return hashlib.sha256(content.encode("utf-8")).hexdigest()
