package com.clanstop.healing.driver;

import com.clanstop.healing.config.FrameworkConfig;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeDriverService;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.GeckoDriverService;

import java.io.File;

/**
 * Per-scenario driver provider. Picocontainer creates one instance per scenario (its no-arg
 * constructor makes it injectable), and both the hooks and the step classes receive that same
 * instance — this is what makes the healing driver available to steps with zero boilerplate.
 *
 * <p>The browser is built lazily on first {@link #get()} so scenarios that never touch the UI
 * pay nothing. Selenium Manager (bundled since Selenium 4.6) downloads the matching driver
 * binary automatically, so there is no WebDriverManager dependency.
 */
public final class DriverManager {

    // A pinned Selenium's Chromium CDP (DevTools) bindings always lag the newest Edge/Chrome, so Selenium
    // logs a noisy "Unable to find CDP implementation matching <N>" WARNING on driver start. Recorded tests
    // use plain WebDriver (navigate/click/type/assert/screenshot) and never touch CDP, so this is harmless
    // noise - quiet those loggers. Strong static refs are kept because JUL can GC a logger (and reset its
    // level) when nothing holds it; exact names are used because a parent-package level isn't inherited here.
    private static final java.util.logging.Logger[] QUIET_LOGGERS = {
        java.util.logging.Logger.getLogger("org.openqa.selenium.devtools.CdpVersionFinder"),
        java.util.logging.Logger.getLogger("org.openqa.selenium.chromium.ChromiumDriver"),
        java.util.logging.Logger.getLogger("org.openqa.selenium.devtools")
    };
    static {
        for (java.util.logging.Logger l : QUIET_LOGGERS) { l.setLevel(java.util.logging.Level.SEVERE); }
    }

    private HealingDriver driver;

    public HealingDriver get() {
        if (driver == null) {
            WebDriver raw = buildBrowser();
            long implicitWaitMs = FrameworkConfig.get().implicitWaitMs();   // mirrors the recorder's "Element wait"
            if (implicitWaitMs > 0) {
                raw.manage().timeouts().implicitlyWait(java.time.Duration.ofMillis(implicitWaitMs));
            }
            driver = new HealingDriver(raw);
        }
        return driver;
    }

    /** Open the configured base URL and return the healing driver. */
    public HealingDriver open(String path) {
        HealingDriver d = get();
        String base = FrameworkConfig.get().baseUrl();
        d.get(path == null || path.isBlank() ? base : base + path);
        return d;
    }

    public boolean started() {
        return driver != null;
    }

    public void quit() {
        if (driver != null) {
            driver.quit();
            driver = null;
            HealingContext.clearCurrentDriver();
        }
    }

    /** Build a RemoteWebDriver against a Selenium Grid / cloud hub using the given capabilities. */
    private static WebDriver remote(String url, org.openqa.selenium.MutableCapabilities options) {
        try {
            return new org.openqa.selenium.remote.RemoteWebDriver(new java.net.URL(url), options);
        } catch (java.net.MalformedURLException e) {
            throw new RuntimeException("Invalid grid / remote hub URL: " + url, e);
        }
    }

    private static WebDriver buildBrowser() {
        FrameworkConfig cfg = FrameworkConfig.get();
        String browser = cfg.browser();
        boolean headless = cfg.headless();
        String driverPath = cfg.driverPath();       // downloaded chromedriver/geckodriver, or null
        String browserBinary = cfg.browserBinary();  // browser executable, or null

        // Cross-browser cloud grid: when a hub is configured, run there (browser chosen by grid.browser)
        // instead of launching a local browser. Covers plain Selenium Grid and BrowserStack/Sauce/LT.
        String hub = GridCapabilities.hubUrl(cfg);
        if (hub != null) {
            return remote(hub, GridCapabilities.build(cfg));
        }

        return switch (browser) {
            case "firefox" -> {
                FirefoxOptions opts = new FirefoxOptions();
                if (headless) {
                    opts.addArguments("-headless");
                }
                if (browserBinary != null) {
                    opts.setBinary(browserBinary);
                }
                if (driverPath != null) {
                    GeckoDriverService service = new GeckoDriverService.Builder()
                            .usingDriverExecutable(new File(driverPath))
                            .build();
                    yield new FirefoxDriver(service, opts);
                }
                yield new FirefoxDriver(opts);
            }
            case "edge" -> {
                EdgeOptions opts = new EdgeOptions();
                if (headless) {
                    opts.addArguments("--headless=new");
                }
                opts.addArguments("--no-sandbox", "--disable-dev-shm-usage", "--window-size=1280,900");
                if (browserBinary != null) {
                    opts.setBinary(browserBinary);
                }
                if (driverPath != null) {
                    EdgeDriverService service = new EdgeDriverService.Builder()
                            .usingDriverExecutable(new File(driverPath))
                            .build();
                    yield new EdgeDriver(service, opts);
                }
                yield new EdgeDriver(opts);
            }
            default -> {
                ChromeOptions opts = new ChromeOptions();
                if (headless) {
                    opts.addArguments("--headless=new");
                }
                opts.addArguments("--no-sandbox", "--disable-dev-shm-usage", "--window-size=1280,900");
                if (browserBinary != null) {
                    opts.setBinary(browserBinary);
                }
                if (driverPath != null) {
                    ChromeDriverService service = new ChromeDriverService.Builder()
                            .usingDriverExecutable(new File(driverPath))
                            .build();
                    yield new ChromeDriver(service, opts);
                }
                yield new ChromeDriver(opts);
            }
        };
    }
}
