import json
import zipfile
import shutil
import tempfile

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

from alayaflow.utils.logger import AlayaFlowLogger
from alayaflow.workflow.workflow_info import WorkflowKey, WorkflowInfo
from alayaflow.workflow.workflow_loader import WorkflowLoader
from alayaflow.workflow.runnable import BaseRunnableWorkflow
from alayaflow.clients.alayahub.http_client import HttpAlayaHubClient
from alayaflow.common.config import settings


logger = AlayaFlowLogger()


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.workflows_dir / 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 unload_workflow(self, workflow_id: str, version: str) -> None:
        key = WorkflowKey(workflow_id, version)
        if key in self._workflow_cache:
            del self._workflow_cache[key]
        if key in self._info_cache:
            del self._info_cache[key]
        if key in self._requirements_cache:
            del self._requirements_cache[key]

    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.workflows_dir / 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.workflows_dir / 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

    ## Loaded Workflows ##

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

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

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

    def list_loaded_workflow_metas(self) -> List[WorkflowInfo]:
        return sorted([wf.info for wf in self._workflow_cache.values()])

    ## Local Workflows ##

    def list_local_workflow_metas(self) -> List[WorkflowInfo]:
        if not settings.workflows_dir.exists():
            raise FileNotFoundError(f"Workflow storage path {settings.workflows_dir} does not exist")
        metas = []
        for workflow_id_dir in settings.workflows_dir.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:
                    meta = self.get_workflow_info(workflow_id, version)
                    metas.append(meta)
                except (FileNotFoundError, ValueError, IOError, ImportError, AttributeError, TypeError) as e:
                    print(f"Loading workflow {workflow_id} version {version} failed: {e}")
                    continue
        sorted_metas = sorted(metas, key=lambda x: (x.id, x.version))
        return sorted_metas

    def list_local_workflows(self) -> List[str]:
        return sorted([f'{wf.id}:{wf.version}' for wf in self.list_local_workflow_metas()])
        
    def list_local_workflow_ids(self) -> List[str]:
        return sorted([wf.id for wf in self.list_local_workflow_metas()])

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

    ## Remote Workflows ##

    def list_remote_workflow_metas(self) -> List[WorkflowInfo]:
        metas = []
        try:
            list_url = f"{settings.alayahub_url}/api/hub/workflow/list"
            logger.info(f"Requesting remote workflows from {list_url}")
            response = requests.get(list_url, timeout=10)
            response.raise_for_status()
            resp = response.json()
            if not isinstance(resp, list):
                logger.error(f"Unexpected response format: {resp}")
                return []
            metas = [WorkflowInfo.from_dict(item) for item in resp]
        except requests.RequestException as e:
            logger.error(f"Failed to list remote workflows: {e}", exc_info=True)
            return []

        return sorted(metas, key=lambda x: (x.id, x.version))

    def list_remote_workflows(self) -> List[str]:
        return sorted([f'{wf.id}:{wf.version}' for wf in self.list_remote_workflow_metas()])

    def list_remote_workflow_ids(self) -> List[str]:
        return sorted([wf.id for wf in self.list_remote_workflow_metas()])

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

    def list_all_workflows(self) -> Dict[str, List[str]]:
        """列举工作流，分别返回已加载、本地已安装和远程未安装的工作流"""
        loaded_workflows = self.list_loaded_workflows()
        local_workflows = self.list_local_workflows()
        remote_workflows = self.list_remote_workflows()
        return {
            "loaded": loaded_workflows,
            "local": local_workflows,
            "remote": remote_workflows,
        }

    def install_workflow(self, workflow_id: str, version: str, force_reinstall: bool = False, workflow_dir: Optional[str] = None) -> bool:
        try:
            if not force_reinstall and f"{workflow_id}:{version}" in self.list_local_workflows():
                logger.info(f"Workflow {workflow_id}:{version} is already loaded. Use force_reinstall=True to reinstall.")
                return False
            
            self.uninstall_workflow(workflow_id, version, raise_error=False, workflow_dir=workflow_dir)
            
            # 创建临时目录存放 zip 文件
            with tempfile.TemporaryDirectory() as temp_dir:
                zip_path = Path(temp_dir) / f"{workflow_id}.zip"

                client = HttpAlayaHubClient(settings.alayahub_url, settings.alayahub_download_url)
                client.download_workflow(workflow_id, version, zip_path)
                
                # 读取 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")
                    
                    # 确定本地工作流目录
                    if workflow_dir:
                        workflow_dir: Path = Path(workflow_dir)
                    else:
                        workflow_dir: Path = settings.workflows_dir / workflow_id / version
                    
                    # 5. 创建目录并解压文件
                    workflow_dir.mkdir(parents=True, exist_ok=True)
                    
                    # 解压所有文件到目标目录
                    zip_ref.extractall(workflow_dir)

                    logger.info(f"工作流 {workflow_id}:{version} 已成功安装到 {workflow_dir.absolute()}")
                    
                    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, version: str = None, raise_error: bool = True, workflow_dir: Optional[str] = None) -> bool:
        try:
            self.unload_workflow(workflow_id, version)

            if workflow_dir:
                workflow_dir = Path(workflow_dir)
            else:
                if version:
                    workflow_dir = settings.workflows_dir / workflow_id / version
                else:
                    workflow_dir = settings.workflows_dir / workflow_id  # 未指定版本时，删除整个工作流目录
            
            if not workflow_dir.exists() or not workflow_dir.is_dir():
                if raise_error:
                    if version:
                        raise ValueError(f"工作流 {workflow_id}:{version} 未安装")
                    else:
                        raise ValueError(f"工作流 {workflow_id} 未安装")
                else:
                    return False
            
            shutil.rmtree(workflow_dir)
            
            return True
            
        except Exception as e:
            raise ValueError(f"卸载工作流 {workflow_id} 失败: {e}") from e
