/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.
 */

#include "iceberg/util/temporal_util.h"

#include <limits>
#include <string>

#include <gtest/gtest.h>

#include "iceberg/test/matchers.h"

namespace iceberg {

TEST(TemporalUtilTest, ParseTimestampNs) {
  ICEBERG_UNWRAP_OR_FAIL(
      auto nanos, TemporalUtils::ParseTimestampNs("2026-01-01T00:00:01.000001001"));
  EXPECT_EQ(nanos, 1767225601000001001L);

  ICEBERG_UNWRAP_OR_FAIL(auto pre_epoch_nanos, TemporalUtils::ParseTimestampNs(
                                                   "1969-12-31T23:59:59.123456789"));
  EXPECT_EQ(pre_epoch_nanos, -876543211);
}

TEST(TemporalUtilTest, ParseTimestampNsChecksInt64Bounds) {
  ICEBERG_UNWRAP_OR_FAIL(
      auto max_nanos, TemporalUtils::ParseTimestampNs("2262-04-11T23:47:16.854775807"));
  EXPECT_EQ(max_nanos, std::numeric_limits<int64_t>::max());

  ICEBERG_UNWRAP_OR_FAIL(
      auto min_nanos, TemporalUtils::ParseTimestampNs("1677-09-21T00:12:43.145224192"));
  EXPECT_EQ(min_nanos, std::numeric_limits<int64_t>::min());

  EXPECT_THAT(TemporalUtils::ParseTimestampNs("2262-04-11T23:47:16.854775808"),
              IsError(ErrorKind::kInvalidArgument));
  EXPECT_THAT(TemporalUtils::ParseTimestampNs("1677-09-21T00:12:43.145224191"),
              IsError(ErrorKind::kInvalidArgument));
}

TEST(TemporalUtilTest, ParseTimestampNsRejectsMoreThanNineFractionalDigits) {
  EXPECT_THAT(TemporalUtils::ParseTimestampNs("2026-01-01T00:00:01.0000010011"),
              IsError(ErrorKind::kInvalidArgument));
}

TEST(TemporalUtilTest, ParseTimestampNsWithZone) {
  ICEBERG_UNWRAP_OR_FAIL(auto nanos, TemporalUtils::ParseTimestampNsWithZone(
                                         "2026-01-01T00:00:01.000001001+00:00"));
  EXPECT_EQ(nanos, 1767225601000001001L);
}

TEST(TemporalUtilTest, ParseTimestampNsWithZoneChecksInt64BoundsAfterOffset) {
  ICEBERG_UNWRAP_OR_FAIL(auto max_nanos, TemporalUtils::ParseTimestampNsWithZone(
                                             "2262-04-12T00:47:16.854775807+01:00"));
  EXPECT_EQ(max_nanos, std::numeric_limits<int64_t>::max());

  ICEBERG_UNWRAP_OR_FAIL(auto min_nanos, TemporalUtils::ParseTimestampNsWithZone(
                                             "1677-09-20T23:12:43.145224192-01:00"));
  EXPECT_EQ(min_nanos, std::numeric_limits<int64_t>::min());

  EXPECT_THAT(
      TemporalUtils::ParseTimestampNsWithZone("2262-04-11T23:47:16.854775807-00:01"),
      IsError(ErrorKind::kInvalidArgument));
  EXPECT_THAT(
      TemporalUtils::ParseTimestampNsWithZone("1677-09-21T00:12:43.145224192+00:01"),
      IsError(ErrorKind::kInvalidArgument));
}

TEST(TemporalUtilTest, ParseTimestampNsWithZoneRejectsOffsetPastPlusMinus1800) {
  EXPECT_THAT(
      TemporalUtils::ParseTimestampNsWithZone("2026-01-01T00:00:01.000001001+18:01"),
      IsError(ErrorKind::kInvalidArgument));
  EXPECT_THAT(
      TemporalUtils::ParseTimestampNsWithZone("2026-01-01T00:00:01.000001001-18:30"),
      IsError(ErrorKind::kInvalidArgument));
}

struct ParseParam {
  std::string name;
  std::string str;
  int64_t value;
  enum Kind { kDay, kTime, kTimestamp, kTimestampTz } kind;
};

class TemporalParseTest : public ::testing::TestWithParam<ParseParam> {};

TEST_P(TemporalParseTest, ParsesCorrectly) {
  const auto& param = GetParam();
  switch (param.kind) {
    case ParseParam::kDay: {
      ICEBERG_UNWRAP_OR_FAIL(auto parsed, TemporalUtils::ParseDay(param.str));
      EXPECT_EQ(parsed, static_cast<int32_t>(param.value));
      break;
    }
    case ParseParam::kTime: {
      ICEBERG_UNWRAP_OR_FAIL(auto parsed, TemporalUtils::ParseTime(param.str));
      EXPECT_EQ(parsed, param.value);
      break;
    }
    case ParseParam::kTimestamp: {
      ICEBERG_UNWRAP_OR_FAIL(auto parsed, TemporalUtils::ParseTimestamp(param.str));
      EXPECT_EQ(parsed, param.value);
      break;
    }
    case ParseParam::kTimestampTz: {
      ICEBERG_UNWRAP_OR_FAIL(auto parsed,
                             TemporalUtils::ParseTimestampWithZone(param.str));
      EXPECT_EQ(parsed, param.value);
      break;
    }
  }
}

struct ParseTimeErrorParam {
  std::string name;
  std::string str;
};

class ParseTimeErrorTest : public ::testing::TestWithParam<ParseTimeErrorParam> {};

TEST_P(ParseTimeErrorTest, ReturnsError) {
  EXPECT_THAT(TemporalUtils::ParseTime(GetParam().str),
              IsError(ErrorKind::kInvalidArgument));
}

INSTANTIATE_TEST_SUITE_P(
    TemporalUtilTest, TemporalParseTest,
    ::testing::Values(
        ParseParam{"DayEpoch", "1970-01-01", 0, ParseParam::kDay},
        ParseParam{"DayNext", "1970-01-02", 1, ParseParam::kDay},
        ParseParam{"DayBeforeEpoch", "1969-12-31", -1, ParseParam::kDay},
        ParseParam{"DayYear999", "0999-12-31", -354286, ParseParam::kDay},
        ParseParam{"DayNonLeap", "1971-01-01", 365, ParseParam::kDay},
        ParseParam{"DayY2K", "2000-01-01", 10957, ParseParam::kDay},
        ParseParam{"Day2026", "2026-01-01", 20454, ParseParam::kDay},
        ParseParam{"TimeMidnight", "00:00", 0, ParseParam::kTime},
        ParseParam{"TimeOneSec", "00:00:01", 1000000, ParseParam::kTime},
        ParseParam{"TimeMillis", "00:00:01.500", 1500000, ParseParam::kTime},
        ParseParam{"TimeOneMillis", "00:00:01.001", 1001000, ParseParam::kTime},
        ParseParam{"TimeMicros", "00:00:01.000001", 1000001, ParseParam::kTime},
        ParseParam{"TimeHourMinSec", "01:02:03", 3723000000, ParseParam::kTime},
        ParseParam{"TimeEndOfDay", "23:59:59", 86399000000, ParseParam::kTime},
        ParseParam{"TimestampEpoch", "1970-01-01T00:00:00", 0, ParseParam::kTimestamp},
        ParseParam{"TimestampOneSec", "1970-01-01T00:00:01", 1000000,
                   ParseParam::kTimestamp},
        ParseParam{"TimestampMillis", "2026-01-01T00:00:01.500", 1767225601500000L,
                   ParseParam::kTimestamp},
        ParseParam{"TimestampOneMillis", "2026-01-01T00:00:01.001", 1767225601001000L,
                   ParseParam::kTimestamp},
        ParseParam{"TimestampMicros", "2026-01-01T00:00:01.000001", 1767225601000001L,
                   ParseParam::kTimestamp},
        ParseParam{"TimestampTzEpoch", "1970-01-01T00:00:00+00:00", 0,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzOneSec", "1970-01-01T00:00:01+00:00", 1000000,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzMillis", "2026-01-01T00:00:01.500+00:00",
                   1767225601500000L, ParseParam::kTimestampTz},
        ParseParam{"TimestampTzOneMillis", "2026-01-01T00:00:01.001+00:00",
                   1767225601001000L, ParseParam::kTimestampTz},
        ParseParam{"TimestampTzMicros", "2026-01-01T00:00:01.000001+00:00",
                   1767225601000001L, ParseParam::kTimestampTz},
        ParseParam{"TimestampTzSuffixZ_Epoch", "1970-01-01T00:00:00Z", 0,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzSuffixZ_Millis", "2026-01-01T00:00:01.500Z",
                   1767225601500000L, ParseParam::kTimestampTz},
        ParseParam{"TimestampTzNegZero_Epoch", "1970-01-01T00:00:00-00:00", 0,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzNegZero_Millis", "2026-01-01T00:00:01.500-00:00",
                   1767225601500000L, ParseParam::kTimestampTz},
        ParseParam{"TimeTruncatesNanos", "00:00:01.123456789", 1123456,
                   ParseParam::kTime},
        ParseParam{"1Digit", "00:00:01.5", 1500000, ParseParam::kTime},
        ParseParam{"2Digits", "00:00:01.50", 1500000, ParseParam::kTime},
        ParseParam{"2DigitsNonZero", "00:00:01.12", 1120000, ParseParam::kTime},
        ParseParam{"4Digits", "00:00:01.0001", 1000100, ParseParam::kTime},
        ParseParam{"TimestampNoSec_Zero", "1970-01-01T00:00", 0, ParseParam::kTimestamp},
        ParseParam{"TimestampNoSec_OneMin", "1970-01-01T00:01", 60000000,
                   ParseParam::kTimestamp},
        ParseParam{"TimestampTzNoSec_Offset", "1970-01-01T00:00+00:00", 0,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzNoSec_OneMin", "1970-01-01T00:01+00:00", 60000000,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzNoSec_Z", "1970-01-01T00:00Z", 0,
                   ParseParam::kTimestampTz},
        ParseParam{"ExtendedYearPlusEpoch", "+1970-01-01", 0, ParseParam::kDay},
        ParseParam{"ExtendedYearPlus2026", "+2026-01-01", 20454, ParseParam::kDay},
        ParseParam{"ExtendedYearMinus2026", "-2026-01-01", -1459509, ParseParam::kDay},
        ParseParam{"TimestampTzPositiveOffset", "1970-01-01T05:00:00+05:00", 0,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzNegativeOffset", "1970-01-01T00:00:00-05:00", 18000000000,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzOffsetWithMillis", "2026-01-01T05:30:01.500+05:30",
                   1767225601500000L, ParseParam::kTimestampTz},
        ParseParam{"TimestampTzNegOffsetToEpoch", "1969-12-31T19:00:00-05:00", 0,
                   ParseParam::kTimestampTz},
        ParseParam{"TimestampTzNoSecWithOffset", "1970-01-01T05:30+05:30", 0,
                   ParseParam::kTimestampTz}),
    [](const ::testing::TestParamInfo<ParseParam>& info) { return info.param.name; });

INSTANTIATE_TEST_SUITE_P(
    TemporalUtilTest, ParseTimeErrorTest,
    ::testing::Values(ParseTimeErrorParam{"EmptyString", ""},
                      ParseTimeErrorParam{"TooShort1Char", "1"},
                      ParseTimeErrorParam{"TooShort2Chars", "12"},
                      ParseTimeErrorParam{"TooShort4Chars", "12:3"},
                      ParseTimeErrorParam{"MissingColon", "1200:00"},
                      ParseTimeErrorParam{"OutofRangeHours", "24:00:00"},
                      ParseTimeErrorParam{"OutofRangeMinutes", "12:60:00"},
                      ParseTimeErrorParam{"OutofRangeSeconds", "12:30:61"},
                      ParseTimeErrorParam{"SpaceInsteadOfColon", "12 30"}),
    [](const ::testing::TestParamInfo<ParseTimeErrorParam>& info) {
      return info.param.name;
    });

}  // namespace iceberg
