from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from ..spec_models import EntitySpec, PortalSourceType, RoleSpec, SolutionSpec
from .chart_compiler import compile_charts
from .form_compiler import compile_entity_form
from .navigation_compiler import compile_navigation
from .package_compiler import compile_package
from .portal_compiler import compile_portal
from .view_compiler import compile_views

@dataclass(slots=True)
class ExecutionStep:
    step_name: str
    resource_type: str
    resource_ref: str
    description: str
    depends_on: list[str] = field(default_factory=list)


@dataclass(slots=True)
class ExecutionPlan:
    steps: list[ExecutionStep]

    def as_dict(self) -> dict[str, Any]:
        return {
            "steps": [
                {
                    "step_name": step.step_name,
                    "resource_type": step.resource_type,
                    "resource_ref": step.resource_ref,
                    "description": step.description,
                    "depends_on": step.depends_on,
                }
                for step in self.steps
            ]
        }


@dataclass(slots=True)
class CompiledRole:
    role_id: str
    name: str
    payload: dict[str, Any]


@dataclass(slots=True)
class CompiledEntity:
    entity_id: str
    display_name: str
    app_create_payload: dict[str, Any]
    form_base_payload: dict[str, Any]
    form_relation_payload: dict[str, Any] | None
    field_specs: dict[str, dict[str, Any]]
    field_labels: dict[str, str]
    workflow_plan: dict[str, Any] | None
    view_plans: list[dict[str, Any]]
    chart_plans: list[dict[str, Any]]
    sample_records: list[dict[str, Any]]


@dataclass(slots=True)
class CompiledSolution:
    normalized_spec: SolutionSpec
    package_payload: dict[str, Any] | None
    roles: list[CompiledRole]
    entities: list[CompiledEntity]
    portal_plan: dict[str, Any] | None
    navigation_plan: list[dict[str, Any]]
    execution_plan: ExecutionPlan

    def as_dict(self) -> dict[str, Any]:
        return {
            "normalized_solution_spec": self.normalized_spec.model_dump(mode="json"),
            "execution_plan": self.execution_plan.as_dict(),
        }


def compile_solution(spec: SolutionSpec) -> CompiledSolution:
    package_payload = compile_package(spec)
    roles = [compile_role(role) for role in spec.roles]
    entities = [compile_entity(entity, include_package=package_payload is not None) for entity in spec.entities]
    _apply_portal_chart_semantics(spec, entities)
    portal_plan = compile_portal(spec)
    navigation_plan = compile_navigation(spec)
    execution_plan = build_execution_plan(spec, package_payload is not None)
    return CompiledSolution(
        normalized_spec=spec,
        package_payload=package_payload,
        roles=roles,
        entities=entities,
        portal_plan=portal_plan,
        navigation_plan=navigation_plan,
        execution_plan=execution_plan,
    )


def compile_entity(entity: EntitySpec, *, include_package: bool) -> CompiledEntity:
    app_create_payload, form_base_payload, form_relation_payload, field_specs, field_labels = compile_entity_form(entity, include_package=include_package)
    workflow_plan = None
    view_plans = compile_views(entity)
    chart_plans = compile_charts(entity)
    return CompiledEntity(
        entity_id=entity.entity_id,
        display_name=entity.display_name,
        app_create_payload=app_create_payload,
        form_base_payload=form_base_payload,
        form_relation_payload=form_relation_payload,
        field_specs=field_specs,
        field_labels=field_labels,
        workflow_plan=workflow_plan,
        view_plans=view_plans,
        chart_plans=chart_plans,
        sample_records=[record.model_dump(mode="json") for record in entity.sample_records],
    )


def compile_role(role: RoleSpec) -> CompiledRole:
    payload = {
        "roleName": role.name,
        "roleIcon": role.icon or role.config.get("roleIcon") or "ex-user-outlined",
        "users": list(role.users),
    }
    return CompiledRole(role_id=role.role_id, name=role.name, payload=payload)


def build_execution_plan(
    spec: SolutionSpec,
    include_package: bool,
    attach_package: bool | None = None,
) -> ExecutionPlan:
    steps: list[ExecutionStep] = []
    attach_package = include_package if attach_package is None else attach_package
    if include_package:
        steps.append(ExecutionStep("package.create", "package", spec.package.name or spec.solution_name, "创建应用包"))
    for role in spec.roles:
        dependencies = ["package.create"] if include_package else []
        steps.append(ExecutionStep(f"role.create.{role.role_id}", "role", role.role_id, f"创建角色 {role.name}", dependencies))
    for entity in spec.entities:
        entity_ref = entity.entity_id
        dependencies = ["package.create"] if include_package else []
        steps.append(ExecutionStep(f"app.create.{entity_ref}", "app", entity_ref, f"创建应用 {entity.display_name}", dependencies.copy()))
        last_form_step = f"app.create.{entity_ref}"
        if attach_package:
            steps.append(
                ExecutionStep(
                    f"package.attach.{entity_ref}",
                    "package_attach",
                    entity_ref,
                    f"将应用加入应用包 {entity.display_name}",
                    [f"app.create.{entity_ref}"],
                )
            )
            last_form_step = f"package.attach.{entity_ref}"
        steps.append(ExecutionStep(f"form.base.{entity_ref}", "form", entity_ref, f"写入基础表单 {entity.display_name}", [last_form_step]))
        last_form_step = f"form.base.{entity_ref}"
        if any(field.type.value == "relation" for field in entity.fields):
            steps.append(ExecutionStep(f"form.relations.{entity_ref}", "form", entity_ref, f"回填关联字段 {entity.display_name}", [f"form.base.{entity_ref}"]))
            last_form_step = f"form.relations.{entity_ref}"
        steps.append(ExecutionStep(f"publish.form.{entity_ref}", "app_publish", entity_ref, f"发布表单 {entity.display_name}", [last_form_step]))

        last_app_step = f"publish.form.{entity_ref}"
        if entity.workflow and entity.workflow.enabled:
            workflow_dependencies = [last_app_step]
            workflow_dependencies.extend(f"role.create.{role_ref}" for role_ref in _collect_workflow_role_refs(entity))
            steps.append(ExecutionStep(f"workflow.{entity_ref}", "workflow", entity_ref, f"搭建流程 {entity.display_name}", workflow_dependencies))
            steps.append(ExecutionStep(f"publish.workflow.{entity_ref}", "app_publish", entity_ref, f"发布流程 {entity.display_name}", [f"workflow.{entity_ref}"]))
            last_app_step = f"publish.workflow.{entity_ref}"
        publish_app_dependencies: list[str] = []
        if entity.views:
            steps.append(ExecutionStep(f"views.{entity_ref}", "view", entity_ref, f"搭建视图 {entity.display_name}", [last_app_step]))
            publish_app_dependencies.append(f"views.{entity_ref}")
        if entity.charts:
            steps.append(ExecutionStep(f"charts.{entity_ref}", "chart", entity_ref, f"搭建报表 {entity.display_name}", [last_app_step]))
            publish_app_dependencies.append(f"charts.{entity_ref}")
        if not publish_app_dependencies:
            publish_app_dependencies.append(last_app_step)
        steps.append(ExecutionStep(f"publish.app.{entity_ref}", "app_publish", entity_ref, f"发布应用 {entity.display_name}", publish_app_dependencies))
        if entity.sample_records:
            seed_dependencies = [f"publish.app.{entity_ref}"]
            seed_dependencies.extend(f"seed_data.{target_entity_id}" for target_entity_id in _collect_seed_dependencies(entity) if target_entity_id != entity.entity_id)
            steps.append(ExecutionStep(f"seed_data.{entity_ref}", "record", entity_ref, f"写入模拟数据 {entity.display_name}", seed_dependencies))
    if _should_create_portal(spec):
        deps = [_entity_terminal_step(entity) for entity in spec.entities]
        steps.append(ExecutionStep("portal.create", "portal", spec.portal.name or f"{spec.solution_name} 首页", "创建门户", deps))
        steps.append(ExecutionStep("publish.portal", "portal_publish", spec.portal.name or f"{spec.solution_name} 首页", "发布门户", ["portal.create"]))
    if _should_create_navigation(spec):
        deps = [_entity_terminal_step(entity) for entity in spec.entities]
        if _should_create_portal(spec):
            deps.append("publish.portal")
        steps.append(ExecutionStep("navigation.create", "navigation", spec.solution_name, "创建导航", deps))
        steps.append(ExecutionStep("publish.navigation", "navigation_publish", spec.solution_name, "发布导航", ["navigation.create"]))
    return ExecutionPlan(steps=steps)


def _should_create_portal(spec: SolutionSpec) -> bool:
    return spec.preferences.create_portal and spec.portal.enabled and bool(spec.portal.sections)


def _should_create_navigation(spec: SolutionSpec) -> bool:
    return spec.preferences.create_navigation and spec.navigation.enabled and bool(spec.navigation.items)


def _collect_workflow_role_refs(entity: EntitySpec) -> list[str]:
    if entity.workflow is None:
        return []
    role_refs: list[str] = []
    for node in entity.workflow.nodes:
        role_refs.extend(node.assignees.get("role_refs", []))
    return list(dict.fromkeys(role_refs))


def _collect_seed_dependencies(entity: EntitySpec) -> list[str]:
    target_entity_ids: list[str] = []
    reference_fields = {field.field_id: field.target_entity_id for field in entity.fields if field.target_entity_id}
    for record in entity.sample_records:
        for field_id, value in record.values.items():
            target_entity_id = reference_fields.get(field_id)
            if not target_entity_id:
                continue
            if value is None:
                continue
            target_entity_ids.append(target_entity_id)
    return list(dict.fromkeys(target_entity_ids))


def _entity_terminal_step(entity: EntitySpec) -> str:
    return f"seed_data.{entity.entity_id}" if entity.sample_records else f"publish.app.{entity.entity_id}"


def _apply_portal_chart_semantics(spec: SolutionSpec, entities: list[CompiledEntity]) -> None:
    if not _should_create_portal(spec):
        return
    portal_chart_refs = _collect_portal_chart_refs(spec)
    if not portal_chart_refs:
        return
    for entity in entities:
        for chart_plan in entity.chart_plans:
            if (entity.entity_id, chart_plan["chart_id"]) not in portal_chart_refs:
                continue
            if chart_plan.get("preserve_portal_query_conditions"):
                continue
            chart_plan["config_payload"]["beforeAggregationFilterMatrix"] = []
            chart_plan["config_payload"]["afterAggregationFilterMatrix"] = []
            chart_plan["config_payload"]["queryConditionFieldIds"] = []
            chart_plan["config_payload"]["queryConditionStatus"] = False
            chart_plan["config_payload"]["queryConditionExact"] = False


def _collect_portal_chart_refs(spec: SolutionSpec) -> set[tuple[str, str]]:
    refs: set[tuple[str, str]] = set()
    for section in spec.portal.sections:
        if section.source_type == PortalSourceType.chart and section.entity_id and section.chart_id:
            refs.add((section.entity_id, section.chart_id))
            continue
        if section.source_type != PortalSourceType.filter:
            continue
        filter_config = section.config.get("filterConfig", [])
        graph_list = section.config.get("graphList", [])
        if isinstance(filter_config, dict):
            graph_list = filter_config.get("graphList", graph_list)
            filter_config = filter_config.get("filterConfig", [])
        refs.update(_collect_chart_refs_from_graphs(graph_list))
        for group in filter_config or []:
            for item in group.get("filterGroupConfig", []) or []:
                refs.update(_collect_chart_refs_from_graphs(item.get("filterCondition", []) or []))
    return refs


def _collect_chart_refs_from_graphs(items: list[dict[str, Any]]) -> set[tuple[str, str]]:
    refs: set[tuple[str, str]] = set()
    for item in items:
        graph_ref = item.get("graphRef") if isinstance(item.get("graphRef"), dict) else {}
        entity_id = item.get("entity_id") or graph_ref.get("entity_id")
        chart_id = item.get("chart_id") or graph_ref.get("chart_id")
        if entity_id and chart_id:
            refs.add((str(entity_id), str(chart_id)))
    return refs
