import json
import zipfile
import shutil
import tempfile

from pathlib import Path
from typing import Dict, List, Optional
import requests

from alayaflow.workflow.workflow_info import WorkflowKey, WorkflowInfo
from alayaflow.workflow.workflow_loader import WorkflowLoader
from alayaflow.workflow.runnable import BaseRunnableWorkflow
from alayaflow.common.config import settings


class WorkflowManager:
    def __init__(self):
        self._info_cache: Dict[WorkflowKey, WorkflowInfo] = {}
        self._workflow_cache: Dict[WorkflowKey, BaseRunnableWorkflow] = {}
        self._requirements_cache: Dict[WorkflowKey, List[str]] = {}
        
    def clear_cache(self):
        self._info_cache.clear()
        self._workflow_cache.clear()
        self._requirements_cache.clear()
    
    def load_workflow(self, workflow_id: str, version: str, init_args: Optional[Dict] = None, workflow_dir: Optional[str] = None) -> None:
        if init_args is None:
            init_args = {}
        if workflow_dir is None:
            workflow_dir = settings.workflow_storage_path / workflow_id / version
        else:
            workflow_dir = Path(workflow_dir)
        loader = WorkflowLoader(workflow_dir, workflow_id, version)
        runnable = loader.load_workflow(init_args)
        requirements = loader.load_requirements()
        self._register_workflow(runnable, requirements)

    def register_workflow(self, runnable: BaseRunnableWorkflow, requirements: List[str] = None) -> None:
        self._register_workflow( runnable, requirements or [])

    def _register_workflow(self, runnable: BaseRunnableWorkflow, requirements: List[str]) -> None:
        info = runnable.info
        key = WorkflowKey(info.id, info.version)
        self._info_cache[key] = info
        self._workflow_cache[key] = runnable
        self._requirements_cache[key] = requirements

    # Keep for future reference
    # def load_workflows(self) -> None:
    #     if not settings.workflow_storage_path.exists():
    #         raise FileNotFoundError(f"Workflow storage path {settings.workflow_storage_path} does not exist")
    #     for workflow_id_dir in settings.workflow_storage_path.iterdir():
    #         if not workflow_id_dir.is_dir() or workflow_id_dir.name.startswith("_"):
    #             continue
    #         workflow_id = workflow_id_dir.name
    #         for version_dir in workflow_id_dir.iterdir():
    #             if not version_dir.is_dir() or version_dir.name.startswith("_"):
    #                 continue
                
    #             version = version_dir.name
    #             try:
    #                 self.load_workflow(workflow_id, version)
    #             except (FileNotFoundError, ValueError, IOError, ImportError, AttributeError, TypeError) as e:
    #                 print(f"Loading workflow {workflow_id} version {version} failed: {e}")
    #                 continue

    def get_workflow_info(self, workflow_id: str, version: str) -> WorkflowInfo:
        key = WorkflowKey(workflow_id, version)
        info = self._info_cache.get(key)
        if info is None:
            workflow_dir = settings.workflow_storage_path / workflow_id / version
            loader = WorkflowLoader(workflow_dir, workflow_id, version)
            info = loader.load_info()
            self._info_cache[key] = info
        return info

    def get_workflow_requirements(self, workflow_id: str, version: str) -> List[str]:
        key = WorkflowKey(workflow_id, version)
        reqs = self._requirements_cache.get(key)
        if reqs is None:
            workflow_dir = settings.workflow_storage_path / workflow_id / version
            loader = WorkflowLoader(workflow_dir, workflow_id, version)
            reqs = loader.load_requirements()
            self._requirements_cache[key] = reqs
        return reqs

    def get_workflow_runnable(self, workflow_id: str, version: str) -> BaseRunnableWorkflow:
        key = WorkflowKey(workflow_id, version)
        runnable = self._workflow_cache.get(key)
        if runnable is None:
            raise ValueError(f"Workflow {workflow_id} version {version} does not loaded. Call load_workflow first.")
        return runnable

    def list_loaded_workflows(self) -> List[str]:
        return [f'{wf.id}_{wf.version}' for wf in self._workflow_cache.keys()]

    def list_loaded_workflow_ids(self) -> List[str]:
        return list(set(wf.id for wf in self._workflow_cache.keys()))

    def list_local_workflow_info_list(self) -> List[WorkflowInfo]:
        if not settings.workflow_storage_path.exists():
            raise FileNotFoundError(f"Workflow storage path {settings.workflow_storage_path} does not exist")
        info_list = []
        for workflow_id_dir in settings.workflow_storage_path.iterdir():
            if not workflow_id_dir.is_dir() or workflow_id_dir.name.startswith("_"):
                continue
            workflow_id = workflow_id_dir.name
            for version_dir in workflow_id_dir.iterdir():
                if not version_dir.is_dir() or version_dir.name.startswith("_"):
                    continue
                
                version = version_dir.name
                try:
                    info = self.get_workflow_info(workflow_id, version)
                    info_list.append(info)
                except (FileNotFoundError, ValueError, IOError, ImportError, AttributeError, TypeError) as e:
                    print(f"Loading workflow {workflow_id} version {version} failed: {e}")
                    continue
        return info_list
        
    def list_local_workflows(self) -> List[str]:
        return list(set(wf.id for wf in self.list_local_workflow_info_list()))

    def list_local_workflow_versions(self, workflow_id: str) -> List[str]:
        return [wf.version for wf in self.list_local_workflow_info_list() if wf.id == workflow_id]

    def list_remote_workflow_ids(self) -> List[str]:
        remote_workflow_ids = []
        try:
            list_url = f"{settings.alayahub_url}/api/workflows/list"
            params = {"page": 1, "page_size": 100}
            
            response = requests.get(list_url, params=params, timeout=10)
            response.raise_for_status()
            
            data = response.json()

            if isinstance(data, dict):
                workflows_data = data.get("workflows", data.get("items", []))
            elif isinstance(data, list):
                workflows_data = data
            else:
                workflows_data = []
            
            for workflow in workflows_data:
                if isinstance(workflow, dict):
                    workflow_id = workflow.get("id") or workflow.get("workflow_id")
                    if workflow_id:
                        remote_workflow_ids.append(workflow_id)
                elif isinstance(workflow, str):
                    remote_workflow_ids.append(workflow)
                        
        except requests.RequestException:
            pass
        
        # 去重后返回
        return list(set(remote_workflow_ids))

    def list_available_workflows(self) -> Dict[str, List[str]]:
        """列举工作流，分别返回本地已安装和远程未安装的工作流"""
        # 获取本地已安装的工作流，格式为 ID_version
        local_workflows = self.list_local_workflows()
        local_set = set(local_workflows)
        
        # 获取远程平台上的工作流列表
        remote_workflows = []
        try:
            list_url = f"{settings.alayahub_url}/api/workflows/list"
            params = {"page": 1, "page_size": 100}  # 获取前100个工作流
            
            response = requests.get(list_url, params=params, timeout=10)
            response.raise_for_status()
            
            data = response.json()

            if isinstance(data, dict):
                workflows_data = data.get("workflows", data.get("items", []))
            elif isinstance(data, list):
                workflows_data = data
            else:
                workflows_data = []
            
            # 提取工作流，格式为 id_version，只保留未安装的
            for workflow in workflows_data:
                if isinstance(workflow, dict):
                    workflow_id = workflow.get("id")
                    workflow_version = workflow.get("version")
                    workflow_key = f"{workflow_id}_{workflow_version}"
                    # 检查该 id_version 是否已在本地安装
                    if workflow_key not in local_set:
                        remote_workflows.append(workflow_key)
                        
        except requests.RequestException:
            pass
        
        return {
            "local": local_workflows,
            "remote": remote_workflows,
        }


    def install_workflow(self, workflow_id: str) -> bool:
        try:
            # 1. 从平台下载工作流 zip 文件
            download_url = f"{settings.alayahub_url}/api/workflows/{workflow_id}/download"
            response = requests.get(download_url, stream=True, timeout=30)
            response.raise_for_status()
            
            # 2. 创建临时目录存放 zip 文件
            with tempfile.TemporaryDirectory() as temp_dir:
                zip_path = Path(temp_dir) / f"{workflow_id}.zip"
                
                # 保存 zip 文件
                with open(zip_path, "wb") as f:
                    for chunk in response.iter_content(chunk_size=8192):
                        f.write(chunk)
                
                # 3. 读取 metadata.json 获取工作流信息
                with zipfile.ZipFile(zip_path, "r") as zip_ref:
                    # 检查是否有 metadata.json
                    metadata_content = None
                    for file_info in zip_ref.namelist():
                        if file_info.endswith("metadata.json"):
                            metadata_content = zip_ref.read(file_info).decode("utf-8")
                            break
                    
                    if not metadata_content:
                        raise ValueError(f"工作流 {workflow_id} 的 zip 文件中未找到 metadata.json")
                    
                    # 解析 metadata.json 获取版本
                    metadata = json.loads(metadata_content)
                    version = metadata.get("version", "1.0.0")
                    
                    # 确定本地工作流目录（格式：workflow_id/version）
                    workflow_id_dir = settings.workflow_storage_path / workflow_id
                    workflow_dir = workflow_id_dir / version
                    
                    # 4. 如果该版本目录已存在，先删除
                    if workflow_dir.exists():
                        shutil.rmtree(workflow_dir)
                    
                    # 5. 创建目录并解压文件
                    workflow_dir.mkdir(parents=True, exist_ok=True)
                    
                    # 解压所有文件到目标目录
                    zip_ref.extractall(workflow_dir)
                    
                    # 6. 清理缓存以便重新加载
                    self.clear_cache()
                    
                    return True
                    
        except requests.RequestException as e:
            raise ValueError(f"下载工作流 {workflow_id} 失败: {e}") from e
        except Exception as e:
            raise ValueError(f"安装工作流 {workflow_id} 失败: {e}") from e
    
    def uninstall_workflow(self, workflow_id: str) -> bool:
        try:
            # 确定工作流目录（格式：workflow_id/）
            workflow_id_dir = settings.workflow_storage_path / workflow_id
            
            if not workflow_id_dir.exists() or not workflow_id_dir.is_dir():
                raise ValueError(f"工作流 {workflow_id} 未安装")
            
            # 删除整个 workflow_id 目录（包含所有版本）
            shutil.rmtree(workflow_id_dir)
            
            # 清理缓存
            self.clear_cache()
            return True
            
        except Exception as e:
            raise ValueError(f"卸载工作流 {workflow_id} 失败: {e}") from e
