
import os
import shutil
import re
from jinja2 import Environment, FileSystemLoader
from .config import CurdGenConfig
from .db import TableMetadata
from typing import List

class Generator:
    def __init__(self, config: CurdGenConfig):
        self.config = config
        template_dir = os.path.join(os.path.dirname(__file__), "templates")
        self.env = Environment(loader=FileSystemLoader(template_dir))
        
    def generate_skeleton(self, overwrite: bool = False):
        output_dir = self.config.gen.output_dir
        base_package = self.config.gen.base_package
        app_dir = os.path.join(output_dir, base_package)
        
        if overwrite and os.path.exists(app_dir):
            shutil.rmtree(app_dir)
            
        # 1. 创建基础目录结构
        dirs = ["core", "shared", "features"]
        os.makedirs(app_dir, exist_ok=True)
        for d in dirs:
            os.makedirs(os.path.join(app_dir, d), exist_ok=True)
            self._touch(os.path.join(app_dir, d, "__init__.py"))
        self._touch(os.path.join(app_dir, "__init__.py"))

        # 2. 生成配置 (app/core/config.py)
        # 注意：配置现在放在 core/config.py 中
        self._render_to_file("core_config.jinja2", os.path.join(app_dir, "core", "config.py"))
        self._render_to_file("core_exceptions.jinja2", os.path.join(app_dir, "core", "exceptions.py"), base_package=base_package)
        self._render_to_file("core_logger.jinja2", os.path.join(app_dir, "core", "logger.py"))
        
        # 3. 生成共享层文件
        self._generate_shared_files()

        # 4. 生成基础数据库文件 (新结构中不再使用)
        # self._generate_basics_database([])

        # 5. 生成主入口 main.py
        self._render_to_file("app_main.jinja2", 
                            os.path.join(app_dir, "main.py"),
                            tables=[], base_package=base_package)

        with open(os.path.join(output_dir, "requirements.txt"), "w") as f:
            f.write("fastapi\nuvicorn[standard]\nsqlalchemy\npsycopg2-binary\npydantic\npydantic-settings\npython-dotenv\npyyaml\nredis\n")

    def generate_crud(self, tables: List[TableMetadata], module_name: str = None, overwrite: bool = False):
        if module_name is None:
            module_name = self.config.gen.default_module
            
        base_package = self.config.gen.base_package
        app_dir = os.path.join(self.config.gen.output_dir, base_package)
        features_dir = os.path.join(app_dir, "features")
        
        # Determine DB folder name and normalize for Python package name
        # 确定数据库文件夹名称并标准化为 Python 包名
        db_name = self.config.db.name or "default_db"
        db_host = self.config.db.host or "localhost"
        
        # 标准化: 将非字母数字字符替换为 _
        raw_db_folder = f"{db_name}_{db_host}"
        db_folder_name = re.sub(r'[^a-zA-Z0-9_]', '_', raw_db_folder)
        
        db_dir = os.path.join(features_dir, db_folder_name)
        os.makedirs(db_dir, exist_ok=True)
        self._touch(os.path.join(db_dir, "__init__.py"))
        
        for table in tables:
            # 表文件夹: schema_table
            schema = self.config.db.schema_name or "public"
            table_folder_name = f"{schema}_{table.name}"
            # 标准化表文件夹名称以防万一
            table_folder_name = re.sub(r'[^a-zA-Z0-9_]', '_', table_folder_name)
            
            table_dir = os.path.join(db_dir, table_folder_name)
            os.makedirs(table_dir, exist_ok=True)
            self._touch(os.path.join(table_dir, "__init__.py"))
            
            # 在表文件夹中生成模型
            self._render_to_file("table_model.jinja2", 
                                os.path.join(table_dir, "model.py"), 
                                base_package=base_package, 
                                table=table)
            
            for op in ["create", "delete", "get_by_id", "get_list", "update"]:
                # 操作 (Op) 文件夹
                op_dir = os.path.join(table_dir, op)
                os.makedirs(op_dir, exist_ok=True)
                self._touch(os.path.join(op_dir, "__init__.py"))
                
                slice_name = "logic"
                
                context = {
                    "base_package": base_package,
                    "slice_name": slice_name,
                    "table": table,
                    "op": op,
                    "method": "POST" if op == "create" else ("DELETE" if op == "delete" else ("PUT" if op == "update" else "GET"))
                }
                
                # 渲染文件
                self._render_to_file("slice_crud.jinja2", os.path.join(op_dir, f"{slice_name}.py"), **context)
                self._render_to_file("slice_run.jinja2", os.path.join(op_dir, "run.py"), **context)
                self._render_to_file("slice_test.jinja2", os.path.join(op_dir, "test.py"), **context)
                self._render_to_file("slice_public.jinja2", os.path.join(op_dir, "public.py"), **context)
                
                # 注册到 main.py
                import_path = f"{base_package}.features.{db_folder_name}.{table_folder_name}.{op}.public"
                router_alias = f"{schema}_{table.name}_{op}_router"
                # 标准化别名
                router_alias = re.sub(r'[^a-zA-Z0-9_]', '_', router_alias)
                
                route_prefix = f"/{db_folder_name}/{schema}/{table.name}/{op.replace('_', '-')}"
                tag = f"{db_folder_name}.{schema}.{table.name}"
                
                self._register_slice_custom(import_path, router_alias, route_prefix, tag)

    def generate_blank_slice(self, module_name: str, slice_name: str, method: str = "GET"):
        base_package = self.config.gen.base_package
        app_dir = os.path.join(self.config.gen.output_dir, base_package)
        
        if not slice_name.startswith(module_name + "_"):
             full_slice_name = f"{module_name}_{slice_name}"
        else:
             full_slice_name = slice_name
             
        slice_dir = os.path.join(app_dir, "features", full_slice_name)
        os.makedirs(slice_dir, exist_ok=True)
        self._touch(os.path.join(slice_dir, "__init__.py"))
        
        context = {
            "base_package": base_package,
            "slice_name": full_slice_name,
            "slice_class_name": full_slice_name.replace("_", " ").title().replace(" ", ""),
            "method": method
        }
        
        self._render_to_file("slice_blank.jinja2", os.path.join(slice_dir, f"{full_slice_name}.py"), **context)
        self._render_to_file("slice_run.jinja2", os.path.join(slice_dir, "run.py"), **context)
        self._render_to_file("slice_test.jinja2", os.path.join(slice_dir, "test.py"), **context)
        self._render_to_file("slice_public.jinja2", os.path.join(slice_dir, "public.py"), **context)
        
        # Keep old registration for blank slices for now, or adapt
        self._register_slice_in_main(full_slice_name)

    def generate(self, tables: List[TableMetadata], overwrite: bool = False):
        self.generate_skeleton(overwrite=overwrite)
        self.generate_crud(tables, overwrite=overwrite)

    def _generate_basics_database(self, tables: List[TableMetadata]):
        # Deprecated/Removed functionality as requested
        pass

    def _generate_shared_files(self):
        output_dir = self.config.gen.output_dir
        base_package = self.config.gen.base_package
        app_dir = os.path.join(output_dir, base_package)
        
        os.makedirs(os.path.join(app_dir, "shared"), exist_ok=True)
        self._touch(os.path.join(app_dir, "shared", "__init__.py"))
        
        self._render_to_file("shared_database.jinja2", 
                            os.path.join(app_dir, "shared", "database.py"),
                            base_package=base_package,
                            db=self.config.db)
        
        if not os.path.exists(os.path.join(app_dir, "shared", "security.py")):
             self._touch(os.path.join(app_dir, "shared", "security.py"))

    def generate_docker_files(self):
        output_dir = self.config.gen.output_dir
        base_package = self.config.gen.base_package
        project_name = os.path.basename(os.path.abspath(output_dir))
        
        docker_dir = os.path.join(output_dir, "docker_file")
        os.makedirs(docker_dir, exist_ok=True)
        
        context = {
            "base_package": base_package,
            "project_name": project_name,
            "project_slug": project_name,
            "db_host": self.config.db.host,
            "db_port": self.config.db.port,
            "db_user": self.config.db.user,
            "db_password": self.config.db.password,
            "db_name": self.config.db.name,
        }
        
        # 1. Generate Dockerfile
        self._render_to_file("docker_dockerfile.jinja2", 
                            os.path.join(docker_dir, "Dockerfile"), 
                            **context)
                            
        # 2. Generate docker-compose.yml
        self._render_to_file("docker_compose.jinja2", 
                            os.path.join(docker_dir, "docker-compose.yml"), 
                            **context)
                            
        # 3. 生成启动脚本 (sh 和 ps1)
        actions = ["start", "stop", "restart"]
        
        for action in actions:
            # Shell scripts
            self._render_to_file("docker_ops_sh.jinja2", 
                                os.path.join(docker_dir, f"{action}_docker.sh"), 
                                action=action,
                                **context)
                                
            # PowerShell scripts
            self._render_to_file("docker_ops_ps.jinja2", 
                                os.path.join(docker_dir, f"{action}_docker.ps1"), 
                                action=action,
                                **context)


    def _register_slice_custom(self, import_path: str, router_alias: str, route_prefix: str, tag: str):
        # 现在由 main.py 动态处理
        pass

    def _register_slice_in_main(self, slice_name: str):
        # 现在由 main.py 动态处理
        pass

    def _render_to_file(self, template_name: str, output_path: str, **kwargs):
        template = self.env.get_template(template_name)
        content = template.render(**kwargs)
        
        # PowerShell 脚本在包含中文字符时，通常需要 BOM (utf-8-sig) 才能被正确识别
        encoding = "utf-8-sig" if output_path.endswith(".ps1") else "utf-8"
        
        with open(output_path, "w", encoding=encoding) as f:
            f.write(content)

    def _touch(self, path: str):
        with open(path, "a"):
            pass
