/*
 * Licensed 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.
 *
 * Copyright 2012-2023 the original author or authors.
 */
package org.assertj.core.api.date;

import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.assertj.core.api.Assertions.registerCustomDateFormat;
import static org.assertj.core.api.Assertions.useDefaultDateFormatsOnly;
import static org.assertj.core.util.DateUtil.parseDatetime;
import static org.assertj.core.util.DateUtil.parseDatetimeWithMs;

import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import org.assertj.core.api.DateAssertBaseTest;
import org.assertj.core.util.DateUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

/**
 * Tests the default date format used when using date assertions with date represented as string.
 *
 * @author Joel Costigliola
 */
class DateAssert_with_string_based_date_representation_Test extends DateAssertBaseTest {

  @Override
  @AfterEach
  public void tearDown() {
    useDefaultDateFormatsOnly();
  }

  @Test
  void date_assertion_using_default_date_string_representation() {

    // datetime with ms is supported
    final Date date1timeWithMS = parseDatetimeWithMs("2003-04-26T03:01:02.999");
    assertThat(date1timeWithMS).isEqualTo("2003-04-26T03:01:02.999");
    // datetime without ms is supported
    final Date datetime = parseDatetime("2003-04-26T03:01:02");
    assertThat(datetime).isEqualTo("2003-04-26T03:01:02.000");
    assertThat(datetime).isEqualTo("2003-04-26T03:01:02");
    // date is supported
    final Date date = DateUtil.parse("2003-04-26");
    assertThat(date).isEqualTo("2003-04-26");
    assertThat(date).isEqualTo("2003-04-26T00:00:00");
    assertThat(date).isEqualTo("2003-04-26T00:00:00.000");
  }

  @Test
  void date_assertion_should_support_timestamp_string_representation() throws ParseException {
    Date date = DateUtil.newTimestampDateFormat().parse("2015-05-08 11:30:00.560");
    String timestampAsString = DateUtil.newTimestampDateFormat().format(new Timestamp(date.getTime()));

    assertThat(date).isEqualTo(timestampAsString);
  }

  @Test
  void date_assertion_should_support_date_with_utc_time_zone_string_representation() throws ParseException {
    SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date date = isoFormat.parse("2003-04-26T00:00:00");

    assertThat(date).isEqualTo("2003-04-26T00:00:00+00:00");
  }

  @Test
  void date_assertion_should_support_date_with_ms_and_utc_time_zone_string_representation() throws ParseException {
    // GIVEN
    SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    // WHEN
    Date date = isoFormat.parse("2003-04-26T00:00:00.123");
    // THEN
    assertThat(date).isEqualTo("2003-04-26T00:00:00.123+00:00");
  }

  @Test
  void date_assertion_should_support_date_with_utc_time_zone_in_different_time_zone_string_representation() throws ParseException {
    SimpleDateFormat isoDateFormatUtc = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    isoDateFormatUtc.setTimeZone(TimeZone.getTimeZone("UTC"));

    SimpleDateFormat isoDateFormatNewYork = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
    isoDateFormatNewYork.setTimeZone(TimeZone.getTimeZone("America/New_York"));

    Date date = isoDateFormatUtc.parse("2003-04-26T00:00:00");
    String newYorkDate = isoDateFormatNewYork.format(date);

    assertThat(date).isEqualTo(newYorkDate);
  }

  @Test
  void should_fail_if_given_date_string_representation_cant_be_parsed_with_default_date_formats() {
    final String dateAsString = "2003/04/26";
    assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(new Date()).isEqualTo(dateAsString))
                                                   .withMessage(format("Failed to parse 2003/04/26 with any of these date formats:%n"
                                                                       +
                                                                       "   [yyyy-MM-dd'T'HH:mm:ss.SSSX,%n" +
                                                                       "    yyyy-MM-dd'T'HH:mm:ss.SSS,%n" +
                                                                       "    yyyy-MM-dd HH:mm:ss.SSS,%n" +
                                                                       "    yyyy-MM-dd'T'HH:mm:ssX,%n" +
                                                                       "    yyyy-MM-dd'T'HH:mm:ss,%n" +
                                                                       "    yyyy-MM-dd]"));
  }

  @Test
  void date_assertion_using_custom_date_string_representation() {
    final Date date = DateUtil.parse("2003-04-26");
    assertThat(date).withDateFormat("yyyy/MM/dd").isEqualTo("2003/04/26");
    assertThat(date).isEqualTo("2003/04/26");
  }

  @Test
  void should_fail_if_given_date_string_representation_cant_be_parsed_with_any_custom_date_formats() {
    final Date date = DateUtil.parse("2003-04-26");
    registerCustomDateFormat("yyyy/MM/dd'T'HH:mm:ss");
    // registering again has no effect
    registerCustomDateFormat("yyyy/MM/dd'T'HH:mm:ss");
    assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(date).withDateFormat("yyyy/MM/dd")
                                                                                     .isEqualTo("2003 04 26"))
                                                   .withMessage(format("Failed to parse 2003 04 26 with any of these date formats:%n"
                                                                       +
                                                                       "   [yyyy/MM/dd'T'HH:mm:ss,%n" +
                                                                       "    yyyy/MM/dd,%n" +
                                                                       "    yyyy-MM-dd'T'HH:mm:ss.SSSX,%n" +
                                                                       "    yyyy-MM-dd'T'HH:mm:ss.SSS,%n" +
                                                                       "    yyyy-MM-dd HH:mm:ss.SSS,%n" +
                                                                       "    yyyy-MM-dd'T'HH:mm:ssX,%n" +
                                                                       "    yyyy-MM-dd'T'HH:mm:ss,%n" +
                                                                       "    yyyy-MM-dd]"));
  }

  @Test
  void date_assertion_using_custom_date_string_representation_then_switching_back_to_defaults_date_formats() {
    final Date date = DateUtil.parse("2003-04-26");
    // chained assertions
    assertThat(date).withDateFormat("yyyy/MM/dd").isEqualTo("2003/04/26")
                    .withDefaultDateFormatsOnly().isEqualTo("2003-04-26");
    // new assertions
    assertThat(date).withDateFormat("yyyy/MM/dd").isEqualTo("2003/04/26");
    assertThat(date).withDefaultDateFormatsOnly().isEqualTo("2003-04-26");
  }

  @Test
  void use_custom_date_formats_set_from_Assertions_entry_point() {
    // GIVEN
    final Date date = DateUtil.parse("2003-04-26");

    registerCustomDateFormat("yyyy/MM/dd'T'HH:mm:ss");

    // WHEN
    // fail : the registered format does not match the given date
    Throwable error = catchThrowable(() -> assertThat(date).isEqualTo("2003/04/26"));

    // THEN
    assertThat(error).isInstanceOf(AssertionError.class)
      .hasMessage(format("Failed to parse 2003/04/26 with any of these date formats:%n" +
                          "   [yyyy/MM/dd'T'HH:mm:ss,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss.SSSX,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss.SSS,%n" +
                          "    yyyy-MM-dd HH:mm:ss.SSS,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ssX,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss,%n" +
                          "    yyyy-MM-dd]"));

    // register the expected custom formats, they are used in the order they have been registered.
    registerCustomDateFormat("yyyy/MM/dd");
    assertThat(date).isEqualTo("2003/04/26");
    // another to register a DateFormat
    registerCustomDateFormat(new SimpleDateFormat("yyyy/MM/dd'T'HH:mm:ss.SSS"));
    // the assertion uses the last custom date format registered.
    assertThat(date).isEqualTo("2003/04/26T00:00:00.000");

    useDefaultDateFormatsOnly();
    assertThat(date).isEqualTo("2003-04-26");
    assertThat(date).isEqualTo("2003-04-26T00:00:00");
    assertThat(date).isEqualTo("2003-04-26T00:00:00.000");
  }

  @Test
  void use_custom_date_formats_first_then_defaults_to_parse_a_date() {
    // GIVEN
    // using default formats should work
    final Date date = DateUtil.parse("2003-04-26");
    assertThat(date).isEqualTo("2003-04-26");

    // WHEN
    // date with a custom format : failure since the default formats don't match.
    Throwable error = catchThrowable(() -> assertThat(date).isEqualTo("2003/04/26"));

    // THEN
    assertThat(error).isInstanceOf(AssertionError.class)
      .hasMessage(format("Failed to parse 2003/04/26 with any of these date formats:%n" +
                          "   [yyyy-MM-dd'T'HH:mm:ss.SSSX,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss.SSS,%n" +
                          "    yyyy-MM-dd HH:mm:ss.SSS,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ssX,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss,%n" +
                          "    yyyy-MM-dd]"));

    // registering a custom date format to make the assertion pass
    registerCustomDateFormat("yyyy/MM/dd");
    assertThat(date).isEqualTo("2003/04/26");
    // the default formats are still available and should work
    assertThat(date).isEqualTo("2003-04-26");
    assertThat(date).isEqualTo("2003-04-26T00:00:00");

    // WHEN
    // but if not format at all matches, it fails.
    error = catchThrowable(() -> assertThat(date).isEqualTo("2003 04 26"));

    // THEN
    assertThat(error).isInstanceOf(AssertionError.class)
      .hasMessage(format("Failed to parse 2003 04 26 with any of these date formats:%n" +
                          "   [yyyy/MM/dd,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss.SSSX,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss.SSS,%n" +
                          "    yyyy-MM-dd HH:mm:ss.SSS,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ssX,%n" +
                          "    yyyy-MM-dd'T'HH:mm:ss,%n" +
                          "    yyyy-MM-dd]"));

    // register a new custom format should work
    registerCustomDateFormat("yyyy MM dd");
    assertThat(date).isEqualTo("2003 04 26");
  }

}
