"""
Material API Handler (V2)

职责：
- 加载物资列表（依赖 Project）
- 物资创建/更新/删除（批量接口）
"""

from typing import List, Dict, Any
from ...datasource.api.handler import BaseApiHandler
from ...datasource.task_result import TaskResult
from ...common.sync_node import SyncNode
from ...datasource.api.errors import (
    ERROR_NODE_DATA_NONE,
    ERROR_VALIDATION_FAILED,
    ERROR_PROJECT_ID_NOT_FOUND
)
from .sync_node import MaterialSyncNode
from schemas.material.material import MaterialResponse, MaterialCreate, MaterialUpdate, MaterialPlanUpdate


class MaterialApiHandler(BaseApiHandler):
    """物资 API Handler"""
    
    def __init__(self):
        super().__init__()
        self._node_type = "material"
        self._node_class = MaterialSyncNode
        self._schema = MaterialResponse
        self.update_schemas = [MaterialCreate, MaterialUpdate, MaterialPlanUpdate]
    
    async def load(self) -> List[SyncNode]:
        """
        加载物资列表
        
        依赖 Project
        使用 GET /material/list?project_id={id} 接口
        """
        try:
            projects = self._collection.filter(node_type="project")
            project_ids = [p.data_id for p in projects if p.data_id]
            
            if not project_ids:
                print("⚠️  No projects found, skipping material load")
                return []
            
            all_materials = []
            for project_id in project_ids:
                try:
                    materials = await api_get_material_list(self.api_client, project_id)
                    
                    for material in materials:
                        # 已验证的 Pydantic model，转换为 dict
                        material_data = material.model_dump()
                        all_materials.append(self._create_node(material_data))
                
                except Exception as e:
                    print(f"❌ Failed to load materials for project {project_id}: {e}")
                    continue
            
            return all_materials
        
        except Exception as e:
            print(f"❌ Failed to load materials: {e}")
            return []
    
    async def create_all(self, nodes: List[SyncNode]) -> List[TaskResult]:
        """批量创建物资"""
        results = []
        for node in nodes:
            result = await self._create_single(node)
            results.append(result)
        return results
    
    async def update_all(self, nodes: List[SyncNode]) -> List[TaskResult]:
        """
        批量更新物资
        
        支持两种更新类型：
        1. MaterialUpdate - 基础信息更新
        2. MaterialPlanUpdate - 计划信息更新
        
        更新逻辑：
        - 对每种 schema，检查是否有差异
        - 如果有差异，调用对应 API
        """
        from pydantic import ValidationError
        
        results = []
        for node in nodes:
            data = node.get_data()
            if not data:
                results.append(TaskResult.failed(node_id=node.node_id, error=ERROR_NODE_DATA_NONE))
                continue
            
            origin_data = node.get_origin_data() or {}
            biz_id = node.data_id or data.get("id")
            if not biz_id:
                results.append(TaskResult.failed(node_id=node.node_id, error="Missing biz_id for update"))
                continue
            
            node_updated = False
            error = None
            steps_payload = self._get_request_steps(node)
            steps_sent = False
            
            # 1. 基础信息更新 (MaterialUpdate)
            force_steps_update = self._steps_changed(node)
            base_diff = self._get_schema_diff(
                MaterialUpdate,
                data,
                origin_data,
                node.node_id,
                force_full_schema=force_steps_update,
            )
            if base_diff:
                try:
                    update_model = MaterialUpdate.model_validate(data)
                    push_id = self._generate_push_id()
                    await api_update_material(
                        self.api_client,
                        update_model,
                        push_id,
                        biz_id,
                        steps=steps_payload if not steps_sent else [],
                    )
                    self._mark_approval_snapshot_synced(node)
                    steps_sent = steps_sent or bool(steps_payload)
                    node_updated = True
                except ValidationError as e:
                    error = f"{ERROR_VALIDATION_FAILED}: {e}"
            
            # 2. 计划信息更新 (MaterialPlanUpdate)
            plan_diff = self._get_schema_diff(MaterialPlanUpdate, data, origin_data, node.node_id)
            if not error and plan_diff:
                try:
                    plan_model = MaterialPlanUpdate.model_validate(data)
                    push_id = self._generate_push_id()
                    # 需要调用对应的计划更新 API
                    await api_update_material_plan(
                        self.api_client,
                        plan_model.model_dump(exclude_unset=True),
                        push_id,
                        biz_id,
                        steps=steps_payload if not steps_sent else [],
                    )
                    self._mark_approval_snapshot_synced(node)
                    steps_sent = steps_sent or bool(steps_payload)
                    node_updated = True
                except ValidationError as e:
                    error = f"{ERROR_VALIDATION_FAILED}: {e}"
            
            # 汇总结果
            if error:
                results.append(TaskResult.failed(node_id=node.node_id, error=error))
            elif node_updated:
                results.append(TaskResult.success(node_id=node.node_id))
            else:
                # 没有任何字段需要更新
                results.append(TaskResult.skipped(node_id=node.node_id, reason="No updatable fields changed"))
        
        return results
    
    async def delete_all(self, nodes: List[SyncNode]) -> List[TaskResult]:
        """批量删除物资"""
        results = []
        for node in nodes:
            result = await self._delete_single(node)
            results.append(result)
        return results
    
    async def _create_single(self, node: SyncNode) -> TaskResult:
        """创建物资"""
        try:
            data = node.get_data()
            if not data:
                return TaskResult.failed(
                    node_id=node.node_id,
                    error=ERROR_NODE_DATA_NONE
                )
            
            push_id = self._generate_push_id()
            
            project_id = node.context.get("project_id")
            if not project_id:
                project_id = data.get("project_id")
            
            if not project_id:
                return TaskResult.failed(
                    node_id=node.node_id,
                    error=ERROR_PROJECT_ID_NOT_FOUND
                )
            
            # 验证并转换为 Pydantic model
            from pydantic import ValidationError
            create_data = {"project_id": project_id, **data}
            try:
                create_model = MaterialCreate.model_validate(create_data)
            except ValidationError as e:
                return TaskResult.failed(
                    node_id=node.node_id,
                    error=f"{ERROR_VALIDATION_FAILED}: {e}"
                )
            
            response = await api_create_material(
                self.api_client,
                create_model,
                push_id,
                steps=self._get_request_steps(node),
            )
            if self.poll_mode == "sync":
                created_id = self._extract_created_id_from_response(response)
                if not created_id:
                    return TaskResult.failed(
                        node_id=node.node_id,
                        error="Missing created_id in create response"
                    )
                return TaskResult.success(node_id=node.node_id, data_id=created_id)
            else:
                return TaskResult.in_progress(node_id=node.node_id, task_id=push_id)
        
        except Exception as e:
            return TaskResult.failed(
                node_id=node.node_id,
                error=f"Create failed: {str(e)}"
            )
    
    async def _update_single(self, node: SyncNode) -> TaskResult:
        """更新物资"""
        if not self.api_client:
            return TaskResult.failed(
                node_id=node.node_id,
                error="api_client not initialized"
            )
        
        try:
            data = node.get_data()
            if not data:
                return TaskResult.failed(
                    node_id=node.node_id,
                    error="Node data is None or validation failed"
                )
            
            push_id = self._generate_push_id()
            biz_id = node.data_id or data.get("id")
            if not biz_id:
                return TaskResult.failed(node_id=node.node_id, error="Missing biz_id for update")
            
            # 验证并转换为 Pydantic model
            from pydantic import ValidationError
            try:
                update_model = MaterialUpdate.model_validate(data)
            except ValidationError as e:
                return TaskResult.failed(
                    node_id=node.node_id,
                    error=f"{ERROR_VALIDATION_FAILED}: {e}"
                )
            
            await api_update_material(self.api_client, update_model, push_id, biz_id)
            return TaskResult.success(node_id=node.node_id)
        
        except Exception as e:
            return TaskResult.failed(
                node_id=node.node_id,
                error=f"Update failed: {str(e)}"
            )
    
    async def _delete_single(self, node: SyncNode) -> TaskResult:
        """删除物资"""
        try:
            push_id = self._generate_push_id()
            biz_id = node.data_id
            if not biz_id:
                return TaskResult.failed(node_id=node.node_id, error="Missing biz_id for delete")
            
            request_data = {
                "push_id": push_id,
                "biz_id": biz_id,
            }
            
            response = await self.api_client.request(
                method="DELETE",
                endpoint="/material/delete",
                data=request_data
            )
            
            if response.get("code") == 200:
                return TaskResult.success(node_id=node.node_id)
            else:
                return TaskResult.failed(
                    node_id=node.node_id,
                    error=f"API returned error: {response.get('message', 'Unknown error')}"
                )
        
        except Exception as e:
            return TaskResult.failed(
                node_id=node.node_id,
                error=f"Delete failed: {str(e)}"
            )
    
    def _create_node(self, data: Dict[str, Any]) -> SyncNode:
        """
        创建物资同步节点
        
        设置 project_id 和 material_id 到 context
        """
        node = self._create_basic_node(data, depend_ids=[])
        
        if data.get("project_id"):
            node.context["project_id"] = data["project_id"]
        node.context["material_id"] = node.data_id
        
        return node


# ========== 静态 API 函数 ==========

async def api_get_material(client, material_id: str) -> Dict[str, Any]:
    """获取单个物资数据 GET /material"""
    response = await client.request(
        method="GET",
        endpoint="/material",
        params={"material_id": material_id}
    )
    if response.get("code") != 200:
        raise Exception(f"API error: {response.get('message', 'Unknown error')}")
    return response.get("data", {})


# ========== 静态 API 函数 ==========

async def api_get_material_list(client, project_id: str) -> List[MaterialResponse]:
    """GET /material/list - 获取物资列表"""
    response = await client.request(
        method="GET",
        endpoint="/material/list",
        params={"project_id": project_id}
    )
    if response.get("code") != 200:
        raise Exception(f"API error: {response.get('message', 'Unknown error')}")
    
    items = response.get("data", [])
    
    # 验证每个返回项
    from pydantic import ValidationError
    validated_items = []
    for item in items:
        try:
            validated_item = MaterialResponse.model_validate(item)
            validated_items.append(validated_item)
        except ValidationError as e:
            print(f"⚠️ Skipping invalid material item: {e}")
            continue
    
    return validated_items


async def api_create_material(client, data: MaterialCreate, push_id: str, *, steps: List[Dict[str, Any]] | None = None) -> Dict[str, Any]:
    """创建物资 POST /material/create"""
    request_data = {"push_id": push_id, "data": data.model_dump(), "steps": steps or []}
    response = await client.request(method="POST", endpoint="/material/create", data=request_data)
    if response.get("code") != 200:
        raise Exception(f"API error: {response.get('message', 'Unknown error')}")
    return response


async def api_update_material(
    client,
    data: MaterialUpdate,
    push_id: str,
    biz_id: str,
    *,
    steps: List[Dict[str, Any]] | None = None,
) -> None:
    """更新物资 PUT /material/update"""
    request_data = {"push_id": push_id, "biz_id": biz_id, "data": data.model_dump(exclude_unset=True), "steps": steps or []}
    response = await client.request(method="PUT", endpoint="/material/update", data=request_data)
    if response.get("code") != 200:
        raise Exception(f"API error: {response.get('message', 'Unknown error')}")


async def api_update_material_plan(
    client,
    data: Dict[str, Any],
    push_id: str,
    biz_id: str,
    *,
    steps: List[Dict[str, Any]] | None = None,
) -> None:
    """更新物资计划 PUT /material/plan"""
    request_data = {"push_id": push_id, "biz_id": biz_id, "data": data, "steps": steps or []}
    response = await client.request(method="PUT", endpoint="/material/plan", data=request_data)
    if response.get("code") != 200:
        raise Exception(f"API error: {response.get('message', 'Unknown error')}")
