// 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;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.openqa.selenium.internal.Require.nonNull;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.internal.Require;

@Tag("UnitTests")
class RequireTest {

  @Test
  void shouldCheckBooleanPrecondition() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.precondition(2 * 2 == 5, "this is %s!", "math"))
        .withMessage("this is math!");
    assertThatCode(() -> Require.precondition(2 * 2 == 4, "this is %s!", "math"))
        .doesNotThrowAnyException();
  }

  @Test
  void shouldReturnObjectArgumentIfItIsNotNull() {
    String arg = "test";
    assertThat(Require.nonNull("x", arg)).isSameAs(arg);
    assertThat(nonNull("x", arg, "it cannot be null")).isSameAs(arg);
  }

  @Test
  void canCheckArgumentForNull() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.nonNull("x", null))
        .withMessage("x must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> nonNull("x", null, "cannot be %s", "null"))
        .withMessage("x cannot be null");
  }

  @Test
  void shouldReturnObjectArgumentFromCheckerObjectIfItIsNotNull() {
    String arg = "test";
    assertThat(Require.argument("x", arg).nonNull()).isSameAs(arg);
    assertThat(Require.argument("x", arg).nonNull("it cannot be null")).isSameAs(arg);
  }

  @Test
  void canCheckArgumentForNullUsingCheckerObject() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("x", (Object) null).nonNull())
        .withMessage("x must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("x", (Object) null).nonNull("%s cannot be null", "it"))
        .withMessage("it cannot be null");
  }

  @Test
  void canCheckArgumentEquality() {
    Object arg1 = null;
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("This", arg1).equalTo("that"))
        .withMessage("This must be set");
    Object arg2 = "that";
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("This", arg2).equalTo("this"))
        .withMessage("This must be equal to `this`");
    assertThat(Require.argument("That", arg2).equalTo("that")).isSameAs(arg2);
  }

  @Test
  void canCheckArgumentClass() {
    Object arg1 = null;
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("This", arg1).instanceOf(Number.class))
        .withMessage("This must be set");
    Object arg2 = "that";
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("This", arg2).instanceOf(Number.class))
        .withMessage("This must be an instance of class java.lang.Number");
    assertThat(Require.argument("That", arg2).instanceOf(String.class)).isSameAs(arg2);
  }

  @Test
  void canCheckDurationArgument() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.nonNegative((Duration) null))
        .withMessage("Duration must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.nonNegative("Timeout", (Duration) null))
        .withMessage("Timeout must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.nonNegative(Duration.ofSeconds(-5)))
        .withMessage("Duration must be 0 or greater");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.nonNegative("Timeout", Duration.ofSeconds(-5)))
        .withMessage("Timeout must be 0 or greater");

    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive((Duration) null))
        .withMessage("Duration must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive("Timeout", (Duration) null))
        .withMessage("Timeout must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive(Duration.ofSeconds(0)))
        .withMessage("Duration must be greater than 0");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive("Timeout", Duration.ofSeconds(-5)))
        .withMessage("Timeout must be greater than 0");

    assertThat(Require.nonNegative(Duration.ofSeconds(0))).isEqualTo(Duration.ofSeconds(0));
    assertThat(Require.nonNegative("Timeout", Duration.ofSeconds(0)))
        .isEqualTo(Duration.ofSeconds(0));
    assertThat(Require.nonNegative(Duration.ofSeconds(5))).isEqualTo(Duration.ofSeconds(5));
    assertThat(Require.nonNegative("Timeout", Duration.ofSeconds(5)))
        .isEqualTo(Duration.ofSeconds(5));

    assertThat(Require.positive(Duration.ofSeconds(10))).isEqualTo(Duration.ofSeconds(10));
    assertThat(Require.positive("Timeout", Duration.ofSeconds(10)))
        .isEqualTo(Duration.ofSeconds(10));
  }

  @Test
  void canCheckIntegerArgument() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.nonNegative("Timeout", (Integer) null))
        .withMessage("Timeout must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.nonNegative("Timeout", -5))
        .withMessage("Timeout must be 0 or greater");
    assertThat(Require.nonNegative("Timeout", 0)).isZero();
    assertThat(Require.nonNegative("Timeout", 5)).isEqualTo(5);

    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive("Timeout", (Integer) null))
        .withMessage("Timeout must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive("Timeout", -5))
        .withMessage("Timeout must be greater than 0");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive("Timeout", 0))
        .withMessage("Timeout must be greater than 0");
    assertThat(Require.positive("Timeout", 5)).isEqualTo(5);
  }

  @Test
  void canCheckIntegersWithMessages() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.positive("Timeout", 0, "Message should only be this"))
        .withMessage("Message should only be this");
  }

  @Test
  void canCheckIntegerArgumentWithCheckerObject() {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(
            () -> Require.argument("Timeout", (Integer) null).greaterThan(5, "It should be longer"))
        .withMessage("Timeout must be set");
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("Timeout", 3).greaterThan(5, "It should be longer"))
        .withMessage("It should be longer");
    assertThat(Require.argument("Timeout", 10).greaterThan(5, "It should be longer")).isEqualTo(10);
  }

  @Test
  void canCheckFileArgument() throws IOException {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("Target", (File) null).isFile())
        .withMessage("Target must be set");
    File tempFile = File.createTempFile("example", "tmp");
    tempFile.deleteOnExit();
    assertThat(Require.argument("Target", tempFile).isFile()).isSameAs(tempFile);
    File dir = tempFile.getParentFile();
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("Target", dir).isFile())
        .withMessage("Target must be a regular file: %s", dir);
    if (!tempFile.delete()) {
      fail("Unable to delete temp file");
    }
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("Target", tempFile).isFile())
        .withMessage("Target must exist: %s", tempFile);
  }

  @Test
  void canCheckDirectoryArgument() throws IOException {
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("Target", (File) null).isDirectory())
        .withMessage("Target must be set");
    File tempFile = File.createTempFile("example", "tmp");
    tempFile.deleteOnExit();
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("Target", tempFile).isDirectory())
        .withMessage("Target must be a directory: %s", tempFile);
    File dir = tempFile.getParentFile();
    assertThat(Require.argument("Target", dir).isDirectory()).isSameAs(dir);
    if (!tempFile.delete()) {
      fail("Unable to delete temp file");
    }
    assertThatExceptionOfType(IllegalArgumentException.class)
        .isThrownBy(() -> Require.argument("Target", tempFile).isDirectory())
        .withMessage("Target must exist: %s", tempFile);
  }

  @Test
  void shouldCheckBooleanState() {
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.stateCondition(2 * 2 == 5, "this is %s!", "math"))
        .withMessage("this is math!");
    assertThatCode(() -> Require.stateCondition(2 * 2 == 4, "this is %s!", "math"))
        .doesNotThrowAnyException();
  }

  @Test
  void canCheckStateForNull() {
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("x", (Object) null).nonNull())
        .withMessage("x must not be null");
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("x", (Object) null).nonNull("must not be %s", "null"))
        .withMessage("x must not be null");
    String arg = "this";
    assertThat(Require.state("x", arg).nonNull()).isSameAs(arg);
    assertThat(Require.state("x", arg).nonNull("test")).isSameAs(arg);
  }

  @Test
  void canCheckStateClass() {
    Object arg1 = null;
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("This", arg1).instanceOf(Number.class))
        .withMessage("This must be set");
    Object arg2 = "that";
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("This", arg2).instanceOf(Number.class))
        .withMessage("This must be an instance of class java.lang.Number");
    assertThat(Require.state("That", arg2).instanceOf(String.class)).isSameAs(arg2);
  }

  @Test
  void canCheckFileState() throws IOException {
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", (File) null).isFile())
        .withMessage("Target must be set");
    File tempFile = File.createTempFile("example", "tmp");
    tempFile.deleteOnExit();
    assertThat(Require.state("Target", tempFile).isFile()).isSameAs(tempFile);
    File dir = tempFile.getParentFile();
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", dir).isFile())
        .withMessage("Target must be a regular file: %s", dir);
    if (!tempFile.delete()) {
      fail("Unable to delete temp file");
    }
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", tempFile).isFile())
        .withMessage("Target must exist: %s", tempFile);
  }

  @Test
  void canCheckDirectoryState() throws IOException {
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", (File) null).isDirectory())
        .withMessage("Target must be set");
    File tempFile = File.createTempFile("example", "tmp");
    tempFile.deleteOnExit();
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", tempFile).isDirectory())
        .withMessage("Target must be a directory: %s", tempFile);
    File dir = tempFile.getParentFile();
    assertThat(Require.state("Target", dir).isDirectory()).isSameAs(dir);
    if (!tempFile.delete()) {
      fail("Unable to delete temp file");
    }
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", tempFile).isDirectory())
        .withMessage("Target must exist: %s", tempFile);
  }

  @Test
  void canCheckFilePathState() throws IOException {
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", (Path) null).isFile())
        .withMessage("Target must be set");
    File tempFile = File.createTempFile("example", "tmp");
    tempFile.deleteOnExit();
    Path tempFilePath = Paths.get(tempFile.toURI());
    assertThat(Require.state("Target", tempFilePath).isFile()).isSameAs(tempFilePath);
    Path dirPath = tempFilePath.getParent();
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", dirPath).isFile())
        .withMessage("Target must be a regular file: %s", dirPath);
    if (!tempFile.delete()) {
      fail("Unable to delete temp file");
    }
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", tempFilePath).isFile())
        .withMessage("Target must exist: %s", tempFilePath);
  }

  @Test
  void canCheckDirectoryPathState() throws IOException {
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", (Path) null).isDirectory())
        .withMessage("Target must be set");
    File tempFile = File.createTempFile("example", "tmp");
    Path tempFilePath = Paths.get(tempFile.toURI());
    tempFile.deleteOnExit();
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", tempFilePath).isDirectory())
        .withMessage("Target must be a directory: %s", tempFilePath);
    Path dirPath = tempFilePath.getParent();
    assertThat(Require.state("Target", dirPath).isDirectory()).isSameAs(dirPath);
    if (!tempFile.delete()) {
      fail("Unable to delete temp file");
    }
    assertThatExceptionOfType(IllegalStateException.class)
        .isThrownBy(() -> Require.state("Target", tempFilePath).isDirectory())
        .withMessage("Target must exist: %s", tempFilePath);
  }
}
