# -*- mode: Python -*-

load("../k8s/Tiltfile", "k8s_find_object_name")
load("../io/Tiltfile", "info", "file_write", "dir_create", "prepare_file")

def apply_files(name, project, debug):
    kustomize_dir = project.get("kustomize_dir", "")
    if kustomize_dir != "":
        info("doing kustomization")
        context = project.get("context")
        yaml = kustomize(context + '/' + kustomize_dir)
        yaml_path = context + "/.tiltbuild/files.yaml"
        prepare_file(yaml_path)
        for line in str(yaml).splitlines():
            file_write(yaml_path, line)
        k8s_yaml(yaml)

def project_enable(name, project, debug):
    """Enable a project in Tilt

    Args:
        name: Is the name of the project.
        project: Is the configuration of the project to enable. See notes below on format.
        debug (optional): Is the debug configuration for the project. See note below on format.

    Notes:
        The project passed in is expected to be in this format:
            {
                "context": ".",
                "image": "rancher/tenant-controller",
                "live_reload_deps": [
                    "main.go",
                    "go.mod",
                    "go.sum",
                    "api",
                    "controllers"
                ],
                "kustomize_dir": "config/default",
                "label": "Tenant",
            },
        The optional debug section is expected to be in this format:
            {
                "continue": true,
                "port": 30000
            },
    """
    if not name:
        fail("you must supply the project name")
    if not project:
        fail("you must supply a project configuration")

    if project.get("op") == "apply":
        return apply_files(name, project, debug)

    label = project.get("label")
    env = project.get("env", {})
    port_forwards, links = get_port_forwards(debug)

    build_go_binary(
        context = project.get("context"),
        reload_deps = project.get("live_reload_deps"),
        debug = debug,
        go_main = project.get("go_main", "main.go"),
        binary_name = "manager",
        label = label,
    )

    build_docker_image(
        image = project.get("image"),
        context = project.get("context"),
        binary_name = "manager",
        additional_docker_helper_commands = project.get("additional_docker_helper_commands", ""),
        additional_docker_build_commands = project.get("additional_docker_build_commands", ""),
        port_forwards = port_forwards,
    )

    context = project.get("context")
    tiltbuild_dir = context + "/.tiltbuild"
    dir_create(tiltbuild_dir)


    kustomize_dir = project.get("kustomize_dir", "")
    if kustomize_dir != "":
        info("doing kustomization")
        yaml = kustomize(context + '/' + kustomize_dir)
        yaml = update_manager(yaml, "manager", debug, env, name, project)
        yaml_path = context + "/.tiltbuild/manifest.yaml"
        prepare_file(yaml_path)
        for line in str(yaml).splitlines():
            file_write(yaml_path, line)
        k8s_yaml(yaml)

        objs = decode_yaml_stream(yaml)
        k8s_resource(
            workload = k8s_find_object_name(objs, "Deployment"),
            #objects = [find_object_qualified_name(objs, "Provider")],
            new_name = label.lower() + "_controller",
            labels = [label, "ALL.controllers"],
            port_forwards = port_forwards,
            links = links,
            #resource_deps = ["provider_crd"],
        )
    info("finished adding project")

def update_manager(yaml, containerName, debug, env, name, project):
    """Enable a project in Tilt

    *****TODO: change this so the deployment is passed in

    Args:
        yaml: the controller yaml.
        debug: Is the debug configuration for the project.
        containerName: the name of the container to update
    Notes:
        The debug section is expected to be in this format:
            {
                "continue": true,
                "port": 30000,
                "verbose": false
            },
    """
    print("update manager")
    debug_port = int(debug.get("port", 0))
    debug_continue = bool(debug.get("continue", "true"))
    debug_verbose = bool(debug.get("verbose", "false"))
    objs = decode_yaml_stream(yaml)
    for o in objs:
        if o["kind"] == "Deployment":
            containers = o["spec"]["template"]["spec"]["containers"]
            for c in containers:
                if c["name"] != containerName:
                    continue
                c["image"] = project.get("image")
                if project.get("command"):
                    c["command"] = project.get("command")
                # Append additional args from the main file to any existing container args
                if project.get("args"):
                    current_args = c.get("args", [])
                    c["args"] = current_args + project.get("args")

                cmd = ["sh", "/start.sh", "/manager"]
                if debug != {}:
                    debugArgs = []
                    # Directly iterate over any existing args without a None check.
                    for arg in c.get("args", []):
                        if arg == "--leader-elect" or arg == "--leader-elect=true":
                            continue
                        debugArgs.append(arg)
                    if debug_verbose:
                        debugArgs.append("--v=5")
                    debugArgs.append("--feature-gates=agent-tls-mode=true,no-cert-manager=true,use-rancher-default-registry=true,use-caapf=false")
                    c["command"] = cmd
                    print(cmd)
                    if len(debugArgs) == 0:
                        c.pop("args", None)
                    else:
                        c["args"] = debugArgs
                    c.pop("readinessProbe", None)
                    c.pop("livenessProbe", None)
                    c.pop("resources", None)
                if env != {}:
                    debugEnv = {}
                    containerEnvVars = c.get("env", [])
                    for containerEnv in containerEnvVars:
                        debugEnv[containerEnv.get("name")] = containerEnv.get("value")
                    for envName in env:
                        debugEnv[envName] = env[envName]
                    newEnv = []
                    for dvName in debugEnv:
                        newEnv.append({"name": dvName, "value": debugEnv[dvName]})
                    c["env"] = newEnv

                # Add volume mount for clusterctl-config for turtles project
                if name == "turtles":
                    if "volumeMounts" not in c:
                        c["volumeMounts"] = []
                    c["volumeMounts"].append({
                        "mountPath": "/config",
                        "name": "clusterctl-config"
                    })

            # Add volumes for turtles project
            if name == "turtles":
                if "volumes" not in o["spec"]["template"]["spec"]:
                    o["spec"]["template"]["spec"]["volumes"] = []
                o["spec"]["template"]["spec"]["volumes"].append({
                    "name": "clusterctl-config",
                    "configMap": {
                        "name": "clusterctl-config"
                    }
                })

    yaml = encode_yaml_stream(objs)
    return yaml

def build_go_binary(context, reload_deps, debug, go_main, binary_name, label):
    # Set up a local_resource build of a go binary. The target repo is expected to have a main.go in
    # the context path or the main.go must be provided via go_main option. The binary is written to .tiltbuild/bin/{$binary_name}.
    # TODO @randomvariable: Race detector mode only currently works on x86-64 Linux.
    # Need to switch to building inside Docker when architecture is mismatched
    os_name = str(local("go env GOOS")).rstrip("\n")
    os_arch = str(local("go env GOARCH")).rstrip("\n")
    race_detector_enabled = debug.get("race_detector", False)
    if race_detector_enabled:
        if os_name != "linux" or os_arch != "amd64":
            fail("race_detector is only supported on Linux x86-64")
        cgo_enabled = "1"
        build_options = "-race"
        ldflags = "-linkmode external -extldflags \"-static\""
    else:
        cgo_enabled = "0"
        build_options = ""
        ldflags = "-extldflags \"-static\""

    debug_port = int(debug.get("port", 0))
    if debug_port != 0:
        # disable optimisations and include line numbers when debugging
        gcflags = "all=-N -l"
    else:
        gcflags = ""

    build_env = "CGO_ENABLED={cgo_enabled} GOOS=linux GOARCH={arch}".format(
        cgo_enabled = cgo_enabled,
        arch = os_arch,
    )

    build_cmd = "{build_env} go build {build_options} -gcflags '{gcflags}' -ldflags '{ldflags}' -tags '{tags}' -o .tiltbuild/bin/{binary_name} {go_main}".format(
        build_env = build_env,
        build_options = build_options,
        gcflags = gcflags,
        go_main = go_main,
        ldflags = ldflags,
        tags = "prime",
        binary_name = binary_name,
    )

    # Prefix each live reload dependency with context. For example, for if the context is
    # test/infra/docker and main.go is listed as a dep, the result is test/infra/docker/main.go. This adjustment is
    # needed so Tilt can watch the correct paths for changes.
    live_reload_deps = []
    for d in reload_deps:
        live_reload_deps.append(context + "/" + d)
    local_resource(
        label.lower() + "_binary",
        cmd = "cd {context};mkdir -p .tiltbuild/bin;{build_cmd}".format(
            context = context,
            build_cmd = build_cmd,
        ),
        deps = live_reload_deps,
        labels = [label, "ALL.binaries"],
    )

def build_docker_image(image, context, binary_name, additional_docker_build_commands, additional_docker_helper_commands, port_forwards):
    links = []

    info("in build_docker_image")
    info(image)
    info(context)


    dockerfile_contents = "\n".join([
        tilt_helper_dockerfile_header,
        additional_docker_helper_commands,
        tilt_dockerfile_header,
        additional_docker_build_commands,
    ])

    # Set up an image build for the provider. The live update configuration syncs the output from the local_resource
    # build into the container.
    docker_build(
        ref = image,
        context = context + "/.tiltbuild/bin/",
        dockerfile_contents = dockerfile_contents,
        build_args = {"binary_name": binary_name},
        target = "tilt",
        only = binary_name,
        live_update = [
            sync(context + "/.tiltbuild/bin/" + binary_name, "/" + binary_name),
            run("sh /restart.sh"),
        ],
    )

def get_port_forwards(debug):
    port_forwards = []
    links = []

    debug_port = int(debug.get("port", 0))
    print("debug port")
    print(debug_port)
    if debug_port != 0:
        port_forwards.append(port_forward(debug_port, 30000))
    
    return port_forwards, links

tilt_helper_dockerfile_header = """
# Tilt image
FROM golang:1.26.4 as tilt-helper
# Support live reloading with Tilt
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/tilt-dev/rerun-process-wrapper/master/restart.sh  && \
    wget --output-document /start.sh --quiet https://raw.githubusercontent.com/tilt-dev/rerun-process-wrapper/master/start.sh && \
    chmod +x /start.sh && chmod +x /restart.sh && chmod +x /go/bin/dlv && \
    touch /process.txt && chmod 0777 /process.txt `# pre-create PID file to allow even non-root users to run the image`
"""

tilt_dockerfile_header = """
FROM gcr.io/distroless/base:debug as tilt
WORKDIR /
COPY --from=tilt-helper /process.txt .
COPY --from=tilt-helper /start.sh .
COPY --from=tilt-helper /restart.sh .
COPY --from=tilt-helper /go/bin/dlv .
COPY $binary_name .
"""
