package net.siegeln.cameleer.saas.provisioning;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.transport.DockerHttpClient;
import com.github.dockerjava.zerodep.ZerodepDockerHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.List;
import java.util.Map;

public class DockerTenantProvisioner implements TenantProvisioner {
    private static final Logger log = LoggerFactory.getLogger(DockerTenantProvisioner.class);

    private final DockerClient docker;
    private final ProvisioningProperties props;

    public DockerTenantProvisioner(DockerClientConfig config, ProvisioningProperties props) {
        this.props = props;
        DockerHttpClient httpClient = new ZerodepDockerHttpClient.Builder()
            .dockerHost(config.getDockerHost())
            .maxConnections(10)
            .connectionTimeout(Duration.ofSeconds(5))
            .responseTimeout(Duration.ofSeconds(30))
            .build();
        this.docker = DockerClientImpl.getInstance(config, httpClient);
    }

    @Override
    public boolean isAvailable() { return true; }

    @Override
    public ProvisionResult provision(TenantProvisionRequest req) {
        String serverName = serverContainerName(req.slug());
        String uiName = uiContainerName(req.slug());
        String endpoint = "http://" + serverName + ":8081";

        try {
            pullIfMissing(props.serverImage());
            pullIfMissing(props.serverUiImage());

            createServerContainer(req, serverName);
            docker.startContainerCmd(serverName).exec();

            createUiContainer(req.slug(), uiName, serverName);
            docker.startContainerCmd(uiName).exec();

            if (!waitForHealth(serverName, 60)) {
                return ProvisionResult.fail("Server did not become healthy within 60s");
            }

            log.info("Provisioned tenant '{}': server={}, ui={}", req.slug(), serverName, uiName);
            return ProvisionResult.ok(endpoint);
        } catch (Exception e) {
            log.error("Failed to provision tenant '{}'", req.slug(), e);
            return ProvisionResult.fail(e.getMessage());
        }
    }

    @Override
    public void start(String slug) {
        try {
            docker.startContainerCmd(serverContainerName(slug)).exec();
            docker.startContainerCmd(uiContainerName(slug)).exec();
        } catch (Exception e) {
            log.error("Failed to start containers for '{}'", slug, e);
            throw new RuntimeException("Start failed: " + e.getMessage(), e);
        }
    }

    @Override
    public void stop(String slug) {
        try {
            stopIfRunning(serverContainerName(slug));
            stopIfRunning(uiContainerName(slug));
        } catch (Exception e) {
            log.error("Failed to stop containers for '{}'", slug, e);
            throw new RuntimeException("Stop failed: " + e.getMessage(), e);
        }
    }

    @Override
    public void remove(String slug) {
        removeContainer(uiContainerName(slug));
        removeContainer(serverContainerName(slug));
    }

    @Override
    public ServerStatus getStatus(String slug) {
        try {
            InspectContainerResponse info = docker.inspectContainerCmd(serverContainerName(slug)).exec();
            String state = info.getState().getStatus();
            String id = info.getId();
            if ("running".equals(state)) return ServerStatus.running(id);
            return ServerStatus.stopped(id);
        } catch (NotFoundException e) {
            return ServerStatus.notFound();
        } catch (Exception e) {
            return ServerStatus.error(e.getMessage());
        }
    }

    @Override
    public String getServerEndpoint(String slug) {
        return "http://" + serverContainerName(slug) + ":8081";
    }

    private void createServerContainer(TenantProvisionRequest req, String name) {
        String slug = req.slug();
        Map<String, String> labels = Map.of(
            "traefik.enable", "true",
            "traefik.http.routers.server-" + slug + ".rule", "PathPrefix(`/t/" + slug + "/api`) || PathPrefix(`/t/" + slug + "/actuator`)",
            "traefik.http.routers.server-" + slug + ".tls", "true",
            "traefik.http.services.server-" + slug + ".loadbalancer.server.port", "8081",
            "cameleer.tenant", slug,
            "cameleer.role", "server"
        );

        List<String> env = List.of(
            "SPRING_DATASOURCE_URL=" + props.datasourceUrl(),
            "SPRING_DATASOURCE_USERNAME=cameleer",
            "SPRING_DATASOURCE_PASSWORD=cameleer_dev",
            "CLICKHOUSE_URL=jdbc:clickhouse://clickhouse:8123/cameleer",
            "CAMELEER_TENANT_ID=" + slug,
            "CAMELEER_AUTH_TOKEN=" + req.licenseToken(),
            "CAMELEER_JWT_SECRET=cameleer-dev-jwt-secret-change-in-production",
            "CAMELEER_OIDC_ISSUER_URI=" + props.oidcIssuerUri(),
            "CAMELEER_OIDC_JWK_SET_URI=" + props.oidcJwkSetUri(),
            "CAMELEER_OIDC_AUDIENCE=https://api.cameleer.local",
            "CAMELEER_OIDC_TLS_SKIP_VERIFY=true",
            "CAMELEER_CORS_ALLOWED_ORIGINS=" + props.corsOrigins(),
            "CAMELEER_LICENSE_TOKEN=" + req.licenseToken(),
            "CAMELEER_RUNTIME_ENABLED=true",
            "CAMELEER_SERVER_URL=http://" + name + ":8081",
            "CAMELEER_ROUTING_DOMAIN=" + props.publicHost(),
            "CAMELEER_ROUTING_MODE=path"
        );

        HostConfig hostConfig = HostConfig.newHostConfig()
            .withRestartPolicy(RestartPolicy.unlessStoppedRestart())
            .withNetworkMode(props.networkName());

        CreateContainerResponse resp = docker.createContainerCmd(props.serverImage())
            .withName(name)
            .withLabels(labels)
            .withEnv(env)
            .withHostConfig(hostConfig)
            .withHealthcheck(new HealthCheck()
                .withTest(List.of("CMD-SHELL", "wget -q -O- http://localhost:8081/actuator/health || exit 1"))
                .withInterval(5_000_000_000L)
                .withTimeout(3_000_000_000L)
                .withRetries(12))
            .exec();

        // Connect to traefik network
        docker.connectToNetworkCmd()
            .withNetworkId(props.traefikNetwork())
            .withContainerId(resp.getId())
            .withContainerNetwork(new ContainerNetwork().withAliases(List.of(name)))
            .exec();
    }

    private void createUiContainer(String slug, String uiName, String serverName) {
        Map<String, String> labels = Map.of(
            "traefik.enable", "true",
            "traefik.http.routers.ui-" + slug + ".rule", "PathPrefix(`/t/" + slug + "`)",
            "traefik.http.routers.ui-" + slug + ".tls", "true",
            "traefik.http.routers.ui-" + slug + ".priority", "1",
            "traefik.http.services.ui-" + slug + ".loadbalancer.server.port", "80",
            "traefik.http.middlewares.ui-strip-" + slug + ".stripprefix.prefixes", "/t/" + slug,
            "traefik.http.routers.ui-" + slug + ".middlewares", "ui-strip-" + slug,
            "cameleer.tenant", slug,
            "cameleer.role", "server-ui"
        );

        List<String> env = List.of(
            "BASE_PATH=/t/" + slug,
            "API_URL=http://" + serverName + ":8081"
        );

        HostConfig hostConfig = HostConfig.newHostConfig()
            .withRestartPolicy(RestartPolicy.unlessStoppedRestart())
            .withNetworkMode(props.networkName());

        CreateContainerResponse resp = docker.createContainerCmd(props.serverUiImage())
            .withName(uiName)
            .withLabels(labels)
            .withEnv(env)
            .withHostConfig(hostConfig)
            .exec();

        docker.connectToNetworkCmd()
            .withNetworkId(props.traefikNetwork())
            .withContainerId(resp.getId())
            .exec();
    }

    private boolean waitForHealth(String containerName, int timeoutSeconds) {
        long deadline = System.currentTimeMillis() + timeoutSeconds * 1000L;
        while (System.currentTimeMillis() < deadline) {
            try {
                InspectContainerResponse info = docker.inspectContainerCmd(containerName).exec();
                InspectContainerResponse.ContainerState state = info.getState();
                if (state.getHealth() != null && "healthy".equals(state.getHealth().getStatus())) {
                    return true;
                }
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } catch (Exception e) {
                log.debug("Health check poll for '{}': {}", containerName, e.getMessage());
                try { Thread.sleep(2000); } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    return false;
                }
            }
        }
        return false;
    }

    private void pullIfMissing(String image) {
        try {
            docker.inspectImageCmd(image).exec();
        } catch (NotFoundException e) {
            log.info("Pulling image: {}", image);
            try {
                docker.pullImageCmd(image).start().awaitCompletion();
            } catch (Exception ex) {
                log.warn("Failed to pull {}: {}", image, ex.getMessage());
            }
        }
    }

    private void stopIfRunning(String name) {
        try {
            docker.stopContainerCmd(name).withTimeout(30).exec();
        } catch (NotFoundException ignored) {}
    }

    private void removeContainer(String name) {
        try {
            docker.removeContainerCmd(name).withForce(true).exec();
        } catch (NotFoundException ignored) {}
    }

    private String serverContainerName(String slug) {
        return "cameleer-server-" + slug;
    }

    private String uiContainerName(String slug) {
        return "cameleer-server-ui-" + slug;
    }
}
