package com.codeborne.selenide.webdriver;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.CommandExecutor;
import org.openqa.selenium.remote.HttpCommandExecutor;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.internal.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field;
import java.time.Duration;

/**
 * A temporary workaround to override default timeouts of OkClient used in Selenium.
 *
 * Its default timeouts are incredibly long:
 * 1) connectTimeout = 120000 ms (2 minutes)
 * 2) readTimeout = 10800000 ms  (3 hours!)
 *
 * If it's fixed in Selenium 4, this workaround can be removed.
 *
 * @since 5.22.0
 */
@ParametersAreNonnullByDefault
class HttpClientTimeouts {
  private static final Logger logger = LoggerFactory.getLogger(HttpClientTimeouts.class);
  public static Duration defaultConnectTimeout = Duration.ofMinutes(1);
  public static Duration defaultReadTimeout = Duration.ofMinutes(2);

  public void setup(WebDriver webDriver) {
    setup(webDriver, defaultConnectTimeout, defaultReadTimeout);
  }

  public void setup(WebDriver webDriver, Duration connectTimeout, Duration readTimeout) {
    if (webDriver instanceof RemoteWebDriver) {
      try {
        setupTimeouts((RemoteWebDriver) webDriver, connectTimeout, readTimeout);
      }
      catch (Exception e) {
        throw new IllegalStateException("Failed to setup Selenium HttpClient timeouts", e);
      }
    }
  }

  private void setupTimeouts(RemoteWebDriver webDriver, Duration connectTimeout, Duration readTimeout) throws Exception {
    CommandExecutor executor = webDriver.getCommandExecutor();
    if (executor instanceof HttpCommandExecutor) {
      setupTimeouts((HttpCommandExecutor) executor, connectTimeout, readTimeout);
    }
  }

  private void setupTimeouts(HttpCommandExecutor executor, Duration connectTimeout, Duration readTimeout) throws Exception {
    Field clientField = HttpCommandExecutor.class.getDeclaredField("client");
    clientField.setAccessible(true);
    HttpClient client = (HttpClient) clientField.get(executor);
    if (client instanceof OkHttpClient) {
      setupTimeouts((OkHttpClient) client, connectTimeout, readTimeout);
    }
  }

  private void setupTimeouts(OkHttpClient client, Duration connectTimeout, Duration readTimeout) throws Exception {
    Field okClientField = OkHttpClient.class.getDeclaredField("client");
    okClientField.setAccessible(true);
    Object okClient = okClientField.get(client);
    if (okClient instanceof okhttp3.OkHttpClient) {
      setupTimeouts((okhttp3.OkHttpClient) okClient, connectTimeout, readTimeout);
    }
  }

  private void setupTimeouts(okhttp3.OkHttpClient okClient, Duration connectTimeout, Duration readTimeout) throws Exception {
    int previousConnectTimeout = okClient.connectTimeoutMillis();
    int previousReadTimeout = okClient.readTimeoutMillis();
    setFieldValue(okClient, "connectTimeout", (int) connectTimeout.toMillis());
    setFieldValue(okClient, "readTimeout", (int) readTimeout.toMillis());
    logger.info("Changed connectTimeout from {} to {}", previousConnectTimeout, okClient.connectTimeoutMillis());
    logger.info("Changed readTimeout from {} to {}", previousReadTimeout, okClient.readTimeoutMillis());
  }

  private <T> void setFieldValue(T object, String fieldName, Object fieldValue) throws Exception {
    Field field = object.getClass().getDeclaredField(fieldName);
    field.setAccessible(true);
    field.set(object, fieldValue);
  }
}

@@ -510,16 +510,79 @@
 uration;
+%0Aimport java.util.List;%0A%0Aimport static java.util.Arrays.asList;
 %0A%0A/**%0A *
@@ -3135,16 +3135,23 @@
 Client, 
+asList(
 %22connect
@@ -3155,24 +3155,49 @@
 ectTimeout%22,
+ %22connectTimeoutMillis%22),
  (int) conne
@@ -3247,16 +3247,23 @@
 Client, 
+asList(
 %22readTim
@@ -3268,16 +3268,38 @@
 imeout%22,
+ %22readTimeoutMillis%22),
  (int) r
@@ -3593,14 +3593,20 @@
 ct, 
+List%3C
 String
+%3E
  fie
@@ -3611,16 +3611,17 @@
 ieldName
+s
 , Object
@@ -3644,32 +3644,137 @@
 ows Exception %7B%0A
+    NoSuchFieldException fieldNotFound = null;%0A    for (String fieldName : fieldNames) %7B%0A      try %7B%0A    
     Field field 
@@ -3818,24 +3818,28 @@
 fieldName);%0A
+    
     field.se
@@ -3857,24 +3857,28 @@
 (true);%0A    
+  
+  
 field.set(ob
@@ -3896,14 +3896,184 @@
 Value);%0A
+        return;%0A      %7D%0A      catch (NoSuchFieldException e) %7B%0A        fieldNotFound = e;%0A      %7D%0A    %7D%0A    if (fieldNotFound != null) %7B%0A      throw fieldNotFound;%0A    %7D%0A
   %7D%0A%7D%0A

add support for okhttp 4.9.1