// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.remote;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import com.google.common.collect.ImmutableMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.ImmutableCapabilities;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrappedWebElement;

class WebElementToJsonConverterTest {

  private static final WebElementToJsonConverter CONVERTER = new WebElementToJsonConverter();

  @Test
  void returnsPrimitivesAsIs() {
    assertThat(CONVERTER.apply(null)).isNull();
    assertThat(CONVERTER.apply("abc")).isEqualTo("abc");
    assertThat(CONVERTER.apply(Boolean.TRUE)).isEqualTo(Boolean.TRUE);
    assertThat(CONVERTER.apply(123)).isEqualTo(123);
    assertThat(CONVERTER.apply(Math.PI)).isEqualTo(Math.PI);
  }

  @Test
  void convertsRemoteWebElementToWireProtocolMap() {
    RemoteWebElement element = new RemoteWebElement();
    element.setId("abc123");

    Object value = CONVERTER.apply(element);
    assertIsWebElementObject(value, "abc123");
  }

  @Test
  void unwrapsWrappedElements() {
    RemoteWebElement element = new RemoteWebElement();
    element.setId("abc123");

    Object value = CONVERTER.apply(wrapElement(element));
    assertIsWebElementObject(value, "abc123");
  }

  @Test
  void unwrapsWrappedElements_multipleLevelsOfWrapping() {
    RemoteWebElement element = new RemoteWebElement();
    element.setId("abc123");

    WrappedWebElement wrapped = wrapElement(element);
    wrapped = wrapElement(wrapped);
    wrapped = wrapElement(wrapped);
    wrapped = wrapElement(wrapped);

    Object value = CONVERTER.apply(wrapped);
    assertIsWebElementObject(value, "abc123");
  }

  @Test
  void convertsSimpleCollections() {
    Object converted = CONVERTER.apply(asList(null, "abc", true, 123, Math.PI));
    assertThat(converted).isInstanceOf(Collection.class);

    List<?> list = new ArrayList<>((Collection<?>) converted);
    assertContentsInOrder(list, null, "abc", true, 123, Math.PI);
  }

  @Test
  void convertsNestedCollections_simpleValues() {
    List<?> innerList = asList(123, "abc");
    List<Object> outerList = asList("apples", "oranges", innerList);

    Object converted = CONVERTER.apply(outerList);
    assertThat(converted).isInstanceOf(Collection.class);

    List<?> list = new ArrayList<>((Collection<?>) converted);
    assertThat(list).hasSize(3);
    assertThat(list.get(0)).isEqualTo("apples");
    assertThat(list.get(1)).isEqualTo("oranges");
    assertThat(list.get(2)).isInstanceOf(Collection.class);

    list = new ArrayList<>((Collection<?>) list.get(2));
    assertContentsInOrder(list, 123, "abc");
  }

  @Test
  void requiresMapsToHaveStringKeys() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> CONVERTER.apply(ImmutableMap.of(new Object(), "bunny")));
  }

  @Test
  void requiresNestedMapsToHaveStringKeys() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(
            () ->
                CONVERTER.apply(
                    ImmutableMap.of(
                        "one", ImmutableMap.of("two", ImmutableMap.of(3, "not good")))));
  }

  @Test
  void convertsASimpleMap() {
    Object converted =
        CONVERTER.apply(ImmutableMap.of("one", 1, "fruit", "apples", "honest", true));
    assertThat(converted).isInstanceOf(Map.class);

    @SuppressWarnings("unchecked")
    Map<String, Object> map = (Map<String, Object>) converted;
    assertThat(map).hasSize(3);
    assertThat(map.get("one")).isEqualTo(1);
    assertThat(map.get("fruit")).isEqualTo("apples");
    assertThat(map.get("honest")).isEqualTo(true);
  }

  @SuppressWarnings("unchecked")
  @Test
  void convertsANestedMap() {
    Object converted =
        CONVERTER.apply(
            ImmutableMap.of(
                "one",
                1,
                "fruit",
                "apples",
                "honest",
                true,
                "nested",
                ImmutableMap.of("bugs", "bunny")));
    assertThat(converted).isInstanceOf(Map.class);

    Map<String, Object> map = (Map<String, Object>) converted;
    assertThat(map).hasSize(4);
    assertThat(map.get("one")).isEqualTo(1);
    assertThat(map.get("fruit")).isEqualTo("apples");
    assertThat(map.get("honest")).isEqualTo(true);
    assertThat(map.get("nested")).isInstanceOf(Map.class);

    map = (Map<String, Object>) map.get("nested");
    assertThat(map.size()).isEqualTo(1);
    assertThat(map.get("bugs")).isEqualTo("bunny");
  }

  @SuppressWarnings("unchecked")
  @Test
  void convertsAListWithAWebElement() {
    RemoteWebElement element = new RemoteWebElement();
    element.setId("abc123");

    RemoteWebElement element2 = new RemoteWebElement();
    element2.setId("anotherId");

    Object value = CONVERTER.apply(asList(element, element2));
    assertThat(value).isInstanceOf(Collection.class);

    List<Object> list = new ArrayList<>((Collection<Object>) value);
    assertThat(list).hasSize(2);
    assertIsWebElementObject(list.get(0), "abc123");
    assertIsWebElementObject(list.get(1), "anotherId");
  }

  @SuppressWarnings("unchecked")
  @Test
  void convertsAMapWithAWebElement() {
    RemoteWebElement element = new RemoteWebElement();
    element.setId("abc123");

    Object value = CONVERTER.apply(ImmutableMap.of("one", element));
    assertThat(value).isInstanceOf(Map.class);

    Map<String, Object> map = (Map<String, Object>) value;
    assertThat(map.size()).isEqualTo(1);
    assertIsWebElementObject(map.get("one"), "abc123");
  }

  @Test
  void convertsAnArray() {
    Object value1 = CONVERTER.apply(new Object[] {"abc123", true, 123, Math.PI});

    assertThat(value1).isInstanceOf(Collection.class);
    assertContentsInOrder(new ArrayList<>((Collection<?>) value1), "abc123", true, 123, Math.PI);

    Object value2 = CONVERTER.apply(new int[] {123, 456, 789});

    assertThat(value2).isInstanceOf(Collection.class);
    assertContentsInOrder(new ArrayList<>((Collection<?>) value2), 123, 456, 789);
  }

  @Test
  void convertsAnArrayWithAWebElement() {
    RemoteWebElement element = new RemoteWebElement();
    element.setId("abc123");

    Object value = CONVERTER.apply(new Object[] {element});
    assertContentsInOrder(
        new ArrayList<>((Collection<?>) value),
        ImmutableMap.of(Dialect.W3C.getEncodedElementKey(), "abc123"));
  }

  @Test
  void shouldConvertShadowRoots() {
    ShadowRoot context = new ShadowRoot(createIdleDriver(), "abc123");
    Object value = CONVERTER.apply(new Object[] {context});
    assertContentsInOrder(
        new ArrayList<>((Collection<?>) value),
        ImmutableMap.of(Dialect.W3C.getShadowRootElementKey(), "abc123"));
  }

  private static WrappedWebElement wrapElement(WebElement element) {
    return new WrappedWebElement(element);
  }

  private static void assertIsWebElementObject(Object value, String expectedKey) {
    assertThat(value).isInstanceOf(Map.class);

    Map<?, ?> map = (Map<?, ?>) value;
    assertThat(map).hasSize(1);
    assertThat(map.containsKey(Dialect.W3C.getEncodedElementKey())).isTrue();
    assertThat(map.get(Dialect.W3C.getEncodedElementKey())).isEqualTo(expectedKey);
  }

  private static void assertContentsInOrder(List<?> list, Object... expectedContents) {
    List<Object> expected = asList(expectedContents);
    assertThat(list).isEqualTo(expected);
  }

  private static RemoteWebDriver createIdleDriver() {
    return new RemoteWebDriver(cmd -> new Response(), new ImmutableCapabilities()) {
      @Override
      protected void startSession(Capabilities capabilities) {
        // Do nothing
      }
    };
  }
}
