package ru.otus.mobile.extensions;

import com.codeborne.selenide.WebDriverRunner;
import com.google.inject.Guice;
import com.google.inject.Injector;
import io.appium.java_client.android.AndroidDriver;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import ru.otus.mobile.annotations.MobileUser;
import ru.otus.mobile.config.MobileConfig;
import ru.otus.mobile.config.TestAccount;
import ru.otus.mobile.config.TestContext;
import ru.otus.mobile.driver.EmulatorQueue;
import ru.otus.mobile.driver.LogcatCollector;
import ru.otus.mobile.driver.MobileDriverFactory;
import ru.otus.mobile.guice.CoreModule;

public final class MobileExtension implements
        TestInstancePostProcessor,
        ParameterResolver,
        AfterTestExecutionCallback,
        AfterEachCallback {
    private static final ExtensionContext.Namespace NAMESPACE =
            ExtensionContext.Namespace.create(MobileExtension.class);
    private static final String SESSION_KEY = "mobile.session";
    private static final Injector INJECTOR = Guice.createInjector(new CoreModule());

    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
        INJECTOR.injectMembers(testInstance);
    }

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
            throws ParameterResolutionException {
        return TestContext.class.equals(parameterContext.getParameter().getType());
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context)
            throws ParameterResolutionException {
        TestAccount account = resolveAccount(context);
        TestContext testContext = createContext(account, context.getDisplayName());
        context.getStore(NAMESPACE).put(SESSION_KEY, testContext);
        return testContext;
    }

    @Override
    public void afterTestExecution(ExtensionContext context) {
        TestContext testContext = context.getStore(NAMESPACE).get(SESSION_KEY, TestContext.class);
        if (testContext != null) {
            INJECTOR.getInstance(LogcatCollector.class).save(testContext);
        }
    }

    @Override
    public void afterEach(ExtensionContext context) {
        TestContext testContext = context.getStore(NAMESPACE).remove(SESSION_KEY, TestContext.class);
        if (testContext != null) {
            closeContext(testContext);
        }
    }

    private TestAccount resolveAccount(ExtensionContext context) {
        MobileUser annotation = context.getRequiredTestClass().getAnnotation(MobileUser.class);
        if (annotation == null) {
            throw new IllegalStateException("Test class must be annotated with @MobileUser.");
        }
        return annotation.value();
    }

    private TestContext createContext(TestAccount account, String testName) {
        EmulatorQueue queue = INJECTOR.getInstance(EmulatorQueue.class);
        MobileConfig.Emulator emulator = queue.acquire();
        try {
            AndroidDriver driver = INJECTOR.getInstance(MobileDriverFactory.class).create(emulator);
            WebDriverRunner.setWebDriver(driver);
            return new TestContext(account, emulator, driver, testName);
        } catch (RuntimeException e) {
            queue.release(emulator);
            throw e;
        }
    }

    private void closeContext(TestContext context) {
        AndroidDriver driver = context.driver();
        EmulatorQueue queue = INJECTOR.getInstance(EmulatorQueue.class);
        try {
            if (driver != null) {
                driver.quit();
            }
        } finally {
            WebDriverRunner.closeWebDriver();
            queue.release(context.emulator());
        }
    }
}
