package com.petshop.backend;

import com.petshop.backend.config.FlywayContextInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;

import java.net.BindException;
import java.time.Duration;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class DevStackApplication {

    private static final Duration WATCH_INTERVAL = Duration.ofSeconds(2);

    public static void main(String[] args) {
        DockerComposeSupport docker = new DockerComposeSupport();
        ConfigurableApplicationContext context = null;
        CountDownLatch shutdownLatch = new CountDownLatch(1);
        ScheduledExecutorService scheduler = null;
        AtomicBoolean shuttingDown = new AtomicBoolean(false);
        RuntimeException startupFailure = null;

        try {
            validateJavaVersion();
            RuntimeClasspathValidator.validate();
            docker.ensureDockerAvailable();
            docker.startDatabase();
            context = new SpringApplicationBuilder(BackendApplication.class)
                    .profiles("local")
                    .initializers(new FlywayContextInitializer())
                    .run(args);
            context.addApplicationListener(event -> {
                if (event instanceof ContextClosedEvent) {
                    shutdownLatch.countDown();
                }
            });
            scheduler = startDatabaseWatch(docker, context, shuttingDown);
            shutdownLatch.await();
        } catch (RuntimeException ex) {
            startupFailure = ex;
            throw new IllegalStateException(describeStartupFailure(ex), ex);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("Dev stack interrupted", ex);
        } finally {
            if (scheduler != null) {
                scheduler.shutdownNow();
            }
            if (context != null && context.isActive()) {
                context.close();
            }
            if (!shuttingDown.get()) {
                try {
                    docker.stopDatabase();
                } catch (RuntimeException stopFailure) {
                    if (startupFailure != null) {
                        System.err.println(stopFailure.getMessage());
                    } else {
                        throw stopFailure;
                    }
                }
            }
        }
    }

    private static void validateJavaVersion() {
        int feature = Runtime.version().feature();
        if (feature < 25) {
            throw new IllegalStateException("JDK 25 or newer is required. IntelliJ is currently using Java " + Runtime.version() + ".");
        }
    }

    private static ScheduledExecutorService startDatabaseWatch(DockerComposeSupport docker, ConfigurableApplicationContext context, AtomicBoolean shuttingDown) {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleWithFixedDelay(() -> {
            if (shuttingDown.get()) {
                return;
            }
            if (!docker.isDatabaseRunning()) {
                shuttingDown.set(true);
                if (context.isActive()) {
                    context.close();
                }
            }
        }, WATCH_INTERVAL.toSeconds(), WATCH_INTERVAL.toSeconds(), TimeUnit.SECONDS);
        return scheduler;
    }

    private static String describeStartupFailure(RuntimeException ex) {
        Throwable cause = deepestCause(ex);
        String message = cause.getMessage();
        String lowerMessage = "";
        if (message != null) {
            lowerMessage = message.toLowerCase(Locale.ROOT);
        }

        switch (classifyStartupFailure(cause, lowerMessage)) {
            case SERVER_PORT_IN_USE:
                return "Backend startup failed because port 8080 is already in use. Stop the other process or set SERVER_PORT to a different value.";
            case DOCKER_NOT_RUNNING:
                return "Backend startup failed because Docker is not available. Start Docker Desktop and try again.";
            case DOCKER_NOT_INSTALLED:
                return "Backend startup failed because Docker is not installed or not on PATH. Install Docker Desktop and reopen IntelliJ.";
            case DATABASE_PORT_IN_USE:
                return "Backend startup failed because port 3306 is already in use. Stop the conflicting MySQL service or container, then run Reset Database and try again.";
            case STALE_SCHEMA:
                return "Backend startup failed because the database schema is stale or incomplete. Run Reset Database and then start Pet Shop Application again.";
            case FLYWAY_MIGRATION_FAILED:
                return "Backend startup failed during Flyway migration. Run Reset Database and check the database logs if the problem persists.";
            case DATASOURCE_CONFIG_MISSING:
                return "Backend startup failed because datasource configuration was not loaded. Reload the Maven project and rerun Pet Shop Application.";
            case DATABASE_UNREACHABLE:
                return "Backend startup failed because it could not connect to MySQL. Make sure Docker Desktop is running, the database container is healthy, and port 3306 is reachable.";
            case UNKNOWN:
                if (message == null || message.isBlank()) {
                    return "Backend startup failed: " + ex.getClass().getSimpleName();
                }
                return "Backend startup failed: " + message;
            default:
                throw new IllegalStateException("Unhandled startup failure classification");
        }
    }

    private static StartupFailure classifyStartupFailure(Throwable cause, String lowerMessage) {
        if (cause instanceof PortInUseException || cause instanceof BindException || lowerMessage.contains("port 8080") && lowerMessage.contains("already in use")) {
            return StartupFailure.SERVER_PORT_IN_USE;
        }
        if (lowerMessage.contains("docker desktop") || lowerMessage.contains("docker daemon") || lowerMessage.contains("docker engine") || lowerMessage.contains("cannot connect to the docker daemon")) {
            return StartupFailure.DOCKER_NOT_RUNNING;
        }
        if (lowerMessage.contains("docker executable") || lowerMessage.contains("no such file or directory") && lowerMessage.contains("docker")) {
            return StartupFailure.DOCKER_NOT_INSTALLED;
        }
        if (lowerMessage.contains("port is already allocated") || lowerMessage.contains("address already in use") && lowerMessage.contains("3306")) {
            return StartupFailure.DATABASE_PORT_IN_USE;
        }
        if (lowerMessage.contains("schema validation: missing table")) {
            return StartupFailure.STALE_SCHEMA;
        }
        if (lowerMessage.contains("flyway") || lowerMessage.contains("migration")) {
            return StartupFailure.FLYWAY_MIGRATION_FAILED;
        }
        if (lowerMessage.contains("datasource properties are required")) {
            return StartupFailure.DATASOURCE_CONFIG_MISSING;
        }
        if (lowerMessage.contains("access denied for user") || lowerMessage.contains("communications link failure") || lowerMessage.contains("connection refused") || lowerMessage.contains("unable to determine dialect without jdbc metadata")) {
            return StartupFailure.DATABASE_UNREACHABLE;
        }
        return StartupFailure.UNKNOWN;
    }

    private static Throwable deepestCause(Throwable throwable) {
        Throwable current = throwable;
        while (current.getCause() != null && current.getCause() != current) {
            current = current.getCause();
        }
        return current;
    }

    private enum StartupFailure {
        SERVER_PORT_IN_USE,
        DOCKER_NOT_RUNNING,
        DOCKER_NOT_INSTALLED,
        DATABASE_PORT_IN_USE,
        STALE_SCHEMA,
        FLYWAY_MIGRATION_FAILED,
        DATASOURCE_CONFIG_MISSING,
        DATABASE_UNREACHABLE,
        UNKNOWN
    }
}
