import re
import yaml
import subprocess
from pathlib import Path
from service_forge.sft.util.logger import log_info, log_error
from service_forge.sft.config.injector_default_files import *
from service_forge.sft.config.sf_metadata import load_metadata, save_metadata
from service_forge.sft.config.sft_config import sft_config
from service_forge.service_config import ServiceConfig, ServiceFeedbackConfig, SignozConfig, TraceConfig, EntryConfig, LLMConfig
from service_forge.workflow.workflow_config import WorkflowConfig
from service_forge.sft.util.name_util import get_service_name
from service_forge.sft.util.yaml_utils import load_sf_metadata_as_json

class Injector:
    def __init__(self, project_dir: Path):
        self.project_dir = project_dir
        self.deployment_yaml_path = project_dir / "deployment.yaml"
        self.metadata_path = project_dir / "sf-meta.yaml"
        self.ingress_yaml_path = project_dir / "ingress.yaml"
        self.dockerfile_path = project_dir / "Dockerfile"
        self.pyproject_toml_path = project_dir / "pyproject.toml"
        self.start_sh_path = project_dir / "start.sh"
        self.metadata = load_metadata(self.metadata_path)
        self.metadata.mode = "release"
        self.name = self.metadata.name
        self.version = self.metadata.version
        self.namespace = sft_config.k8s_namespace

        try:
            self.sf_metadata_string = load_sf_metadata_as_json(self.metadata_path)
        except Exception as e:
            log_error(f"Failed to load sf-metadata as JSON: {e}")
            self.sf_metadata_string = ""

    @property
    def service_name(self) -> str:
        return get_service_name(self.name, self.version)

    def inject_deployment(self) -> None:
        # JSON 字符串已经是紧凑格式，不需要转义换行符
        json_metadata = self.sf_metadata_string

        envs = {
            "DEEPSEEK_API_KEY": sft_config.deepseek_api_key,
            "DEEPSEEK_BASE_URL": sft_config.deepseek_base_url,
        }

        for env in self.metadata.env:
            envs[env['name']] = env['value']

        env_str = ""
        for key, value in envs.items():
            env_str += f'          - name: {key}\n            value: "{value}"\n'

        # Try to get current git branch and short sha to inject as labels.
        def _sanitize_label_value(v: str, max_len: int = 63) -> str:
            if v is None:
                return ""
            vv = str(v).strip()
            # replace slashes and spaces with '-', keep safe chars
            vv = re.sub(r"[\s/]+", "-", vv)
            vv = re.sub(r"[^0-9A-Za-z_.-]", "", vv)
            vv = vv.lower()
            if len(vv) > max_len:
                vv = vv[:max_len]
            return vv

        git_branch = ""
        git_sha = ""
        try:
            # get branch name
            res = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=self.project_dir, capture_output=True, text=True, check=True)
            git_branch = _sanitize_label_value(res.stdout)
        except Exception:
            git_branch = ""
        try:
            # get short sha
            res2 = subprocess.run(["git", "rev-parse", "--short", "HEAD"], cwd=self.project_dir, capture_output=True, text=True, check=True)
            git_sha = _sanitize_label_value(res2.stdout)
        except Exception:
            git_sha = ""

        deployment_yaml = DEFAULT_DEPLOYMENT_YAML.format(
            registry_address=sft_config.registry_address,
            service_name=self.service_name,
            name=self.name,
            version=self.version,
            namespace=self.namespace,
            sf_metadata=f"'{json_metadata}'",
            env=env_str,
            git_branch=git_branch,
            git_sha=git_sha,
        )
        with open(self.deployment_yaml_path, "w") as f:
            f.write(deployment_yaml)
        print("deployment_yaml_path: ", self.deployment_yaml_path)

    def inject_service_config(self) -> None:
        service_config_path = self.project_dir / Path(self.metadata.service_config)

        config = ServiceConfig.from_yaml_file(service_config_path)

        config.http_port = sft_config.inject_http_port
        config.kafka_host = sft_config.inject_kafka_host
        config.kafka_port = sft_config.inject_kafka_port
        if config.databases is not None:
            for database in config.databases:
                if database.postgres_host is not None:
                    database.postgres_host = sft_config.inject_postgres_host
                    database.postgres_port = sft_config.inject_postgres_port
                    database.postgres_user = sft_config.inject_postgres_user
                    database.postgres_password = sft_config.inject_postgres_password
                    database.postgres_db = self.service_name
                if database.mongo_host is not None:
                    database.mongo_host = sft_config.inject_mongo_host
                    database.mongo_port = sft_config.inject_mongo_port
                    database.mongo_user = sft_config.inject_mongo_user
                    database.mongo_password = sft_config.inject_mongo_password
                    database.mongo_db = sft_config.inject_mongo_db
                if database.redis_host is not None:
                    database.redis_host = sft_config.inject_redis_host
                    database.redis_port = sft_config.inject_redis_port
                    database.redis_password = sft_config.inject_redis_password
        if config.feedback is not None:
            config.feedback.api_url = sft_config.inject_feedback_api_url
            config.feedback.api_timeout = sft_config.inject_feedback_api_timeout
        else:
            config.feedback = ServiceFeedbackConfig(
                api_url=sft_config.inject_feedback_api_url,
                api_timeout=sft_config.inject_feedback_api_timeout,
            )

        if config.signoz is not None:
            config.signoz.api_url = sft_config.inject_signoz_api_url
            config.signoz.api_key = sft_config.inject_signoz_api_key
        else:
            config.signoz = SignozConfig(
                api_url=sft_config.inject_signoz_api_url,
                api_key=sft_config.inject_signoz_api_key,
            )

        if config.trace is not None:
            config.trace.enable = True
            config.trace.url = sft_config.inject_trace_url
            config.trace.headers = sft_config.inject_trace_headers
            config.trace.arg = sft_config.inject_trace_arg
            config.trace.namespace = sft_config.inject_trace_namespace
            config.trace.hostname = sft_config.inject_trace_hostname
        else:
            config.trace = TraceConfig(
                enable=True,
                url=sft_config.inject_trace_url,
                headers=sft_config.inject_trace_headers,
                arg=sft_config.inject_trace_arg,
                namespace=sft_config.inject_trace_namespace,
                hostname=sft_config.inject_trace_hostname,
            )

        if config.entry is not None:
            config.entry.url = sft_config.inject_entry_url
        else:
            config.entry = EntryConfig(
                url=sft_config.inject_entry_url,
            )

        if config.llm is not None:
            config.llm.api_base = sft_config.inject_llm_api_base
            config.llm.api_key = sft_config.inject_llm_api_key
        else:
            config.llm = LLMConfig(
                api_base=sft_config.inject_llm_api_base,
                api_key=sft_config.inject_llm_api_key,
            )

        with open(service_config_path, "w", encoding="utf-8") as f:
            yaml.dump(config.model_dump(), f, allow_unicode=True, indent=2)

    def inject_ingress(self) -> None:
        service_config_path = self.project_dir / Path(self.metadata.service_config)
        config = ServiceConfig.from_yaml_file(service_config_path)

        ingress_yaml = DEFAULT_TRAEFIK_INGRESS_YAML.format(
            name=self.name,
            version=self.version.replace(".", "-"),
            namespace=self.namespace,
            auth_middleware_yaml=DEFAULT_AUTH_MIDDLEWARE_YAML.format(namespace=self.namespace) if config.enable_auth else "",
        )

        for workflow_path in config.workflows:
            workflow_config_path = self.project_dir / Path(self.metadata.service_config).parent / Path(workflow_path)
            if workflow_config_path.exists():
                workflow_config = WorkflowConfig.from_yaml_file(workflow_config_path)
                if config.enable_auth != workflow_config.enable_auth:
                    for node in workflow_config.nodes:
                        if node.type == 'FastAPITrigger' and node.args is not None:
                            path = ""
                            method = ""

                            for key, value in node.args.items():
                                if key == 'path':
                                    path = str(value)
                                if key == 'method':
                                    method = str(value).upper()

                            path = re.sub(r'\{.*?\}', '', path)

                            ingress_yaml += DEFAULT_AUTH_WORKFLOW_YAML.format(
                                name=self.name,
                                version=self.version.replace(".", "-"),
                                namespace=self.namespace,
                                path=path.lstrip('/'),
                                method=method,
                                auth_middleware_yaml=DEFAULT_AUTH_MIDDLEWARE_YAML.format(namespace=self.namespace) if workflow_config.enable_auth else "",
                            )

        with open(self.ingress_yaml_path, "w") as f:
            f.write(ingress_yaml)
        print("ingress_yaml_path: ", self.ingress_yaml_path)

    def inject_dockerfile(self) -> None:
        dockerfile = DEFAULT_DOCKERFILE.format(
            registry_address=sft_config.registry_address,
        )
        with open(self.dockerfile_path, "w") as f:
            f.write(dockerfile)
        print("dockerfile_path: ", self.dockerfile_path)

    def inject_pyproject_toml(self) -> None:
        pyproject_toml = DEFAULT_PYPROJECT_TOML
        with open(self.pyproject_toml_path, "r") as f:
            existing_pyproject_toml = f.read()
        if pyproject_toml.strip() not in existing_pyproject_toml.strip():
            with open(self.pyproject_toml_path, "a") as f:
                f.write(pyproject_toml)
        print("pyproject_toml_path: ", self.pyproject_toml_path)

    def clear_start_sh(self) -> None:
        if Path(self.start_sh_path).exists():
            with open(self.start_sh_path, "rb") as f:
                content = f.read()
            content_str = content.decode("utf-8")
            lines = content_str.splitlines()
            new_content = "\n".join(lines) + ("\n" if content_str.endswith(('\n', '\r')) else "")
            with open(self.start_sh_path, "w", encoding="utf-8", newline="\n") as f:
                f.write(new_content)

    def inject(self) -> None:
        if self.metadata.inject.pyproject_toml:
            self.inject_pyproject_toml()
        if self.metadata.inject.deployment:
            self.inject_deployment()
        if self.metadata.inject.service_config:
            self.inject_service_config()
        if self.metadata.inject.ingress:
            self.inject_ingress()
        if self.metadata.inject.dockerfile:
            self.inject_dockerfile()
        if self.metadata.inject.pyproject_toml:
            self.inject_pyproject_toml()
        self.clear_start_sh()
        save_metadata(self.metadata, self.metadata_path)
