/*
 * 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 "paimon/common/metrics/histogram_windowing.h"

#include <chrono>
#include <thread>

#include "gtest/gtest.h"

namespace paimon::test {

TEST(HistogramWindowingImplTest, TestAdvanceAndAggregateAcrossWindows) {
    // Use a relatively large span to avoid flakiness around boundary (aligned_now - start == span).
    HistogramWindowingImpl h(/*num_windows=*/4, /*micros_per_window=*/25000,
                             /*min_num_per_window=*/1);
    h.Add(1);
    std::this_thread::sleep_for(std::chrono::milliseconds(26));
    h.Add(2);

    HistogramStats s = h.GetStats();
    EXPECT_EQ(s.count, 2);
    EXPECT_DOUBLE_EQ(s.min, 1);
    EXPECT_DOUBLE_EQ(s.max, 2);
    EXPECT_NEAR(s.average, 1.5, 1e-12);
}

TEST(HistogramWindowingImplTest, TestResetWhenBeyondMaxSpan) {
    HistogramWindowingImpl h(/*num_windows=*/2, /*micros_per_window=*/1000,
                             /*min_num_per_window=*/1);
    h.Add(1);
    std::this_thread::sleep_for(std::chrono::milliseconds(2));
    h.Add(2);

    // Sleep long enough so that aligned_now - current_window_start >=
    // micros_per_window*num_windows.
    std::this_thread::sleep_for(std::chrono::milliseconds(3));
    h.Add(3);

    HistogramStats s = h.GetStats();
    EXPECT_EQ(s.count, 1);
    EXPECT_DOUBLE_EQ(s.min, 3);
    EXPECT_DOUBLE_EQ(s.max, 3);
    EXPECT_NEAR(s.average, 3.0, 1e-12);
}

TEST(HistogramWindowingImplTest, TestMergeSameParams) {
    // Use a large window to avoid relying on wall clock for this test.
    HistogramWindowingImpl h1(/*num_windows=*/4, /*micros_per_window=*/1000ULL * 1000ULL,
                              /*min_num_per_window=*/1);
    HistogramWindowingImpl h2(/*num_windows=*/4, /*micros_per_window=*/1000ULL * 1000ULL,
                              /*min_num_per_window=*/1);
    h1.Add(1);
    h1.Add(2);
    h2.Add(3);

    h1.Merge(h2);
    HistogramStats s = h1.GetStats();
    EXPECT_EQ(s.count, 3);
    EXPECT_DOUBLE_EQ(s.sum, 6);
    EXPECT_DOUBLE_EQ(s.min, 1);
    EXPECT_DOUBLE_EQ(s.max, 3);
}

TEST(HistogramWindowingImplTest, TestMergeDifferentParamsFallback) {
    HistogramWindowingImpl h1(/*num_windows=*/4, /*micros_per_window=*/1000ULL * 1000ULL,
                              /*min_num_per_window=*/1);
    HistogramWindowingImpl h2(/*num_windows=*/8, /*micros_per_window=*/1000ULL * 1000ULL,
                              /*min_num_per_window=*/1);
    h2.Add(10);
    h2.Add(20);

    h1.Merge(h2);
    HistogramStats s = h1.GetStats();
    EXPECT_EQ(s.count, 2);
    EXPECT_DOUBLE_EQ(s.min, 10);
    EXPECT_DOUBLE_EQ(s.max, 20);
}

TEST(HistogramWindowingImplTest, TestMinNumPerWindow100Case) {
    // Validate min_num_per_window behavior:
    // - window advancement is gated by sample count
    // - if the histogram doesn't advance in time, it may get reset once beyond max span
    // Use a larger window to reduce wall-clock sensitivity and avoid flakiness.
    HistogramWindowingImpl h(/*num_windows=*/3, /*micros_per_window=*/50000,
                             /*min_num_per_window=*/100);

    // Fill current window but keep it below min_num.
    for (int32_t i = 0; i < 99; ++i) {
        h.Add(1);
    }

    // Cross at least one window.
    std::this_thread::sleep_for(std::chrono::milliseconds(75));
    h.Add(2);  // 100th sample; advancement check happens before this add.

    HistogramStats s1 = h.GetStats();
    EXPECT_EQ(s1.count, 100);
    EXPECT_DOUBLE_EQ(s1.min, 1);
    EXPECT_DOUBLE_EQ(s1.max, 2);

    // Cross enough time so that aligned_now - current_window_start >= max_span
    // (max_span = num_windows * micros_per_window = 150ms here).
    std::this_thread::sleep_for(std::chrono::milliseconds(175));
    h.Add(1000);

    HistogramStats s2 = h.GetStats();
    EXPECT_EQ(s2.count, 1);
    EXPECT_DOUBLE_EQ(s2.min, 1000);
    EXPECT_DOUBLE_EQ(s2.max, 1000);
}

TEST(HistogramWindowingImplTest, TestLargeDatasetInSingleWindow) {
    // Use a large window to avoid relying on wall clock.
    HistogramWindowingImpl h(/*num_windows=*/4, /*micros_per_window=*/60ULL * 1000ULL * 1000ULL,
                             /*min_num_per_window=*/1);

    constexpr uint64_t n = 10000;
    for (uint64_t i = 1; i <= n; ++i) {
        h.Add(static_cast<double>(i));
    }

    HistogramStats s = h.GetStats();
    EXPECT_EQ(s.count, n);
    EXPECT_DOUBLE_EQ(s.min, 1);
    EXPECT_DOUBLE_EQ(s.max, static_cast<double>(n));
    EXPECT_DOUBLE_EQ(s.sum, static_cast<double>(n) * static_cast<double>(n + 1) / 2.0);
    EXPECT_NEAR(s.average, (static_cast<double>(n) + 1.0) / 2.0, 1e-12);
}

TEST(HistogramWindowingImplTest, TestCrossMaxSpanAfterAdvance) {
    // Build multiple windows, then cross max_span so that all previous windows are dropped.
    HistogramWindowingImpl h(/*num_windows=*/2, /*micros_per_window=*/2000,
                             /*min_num_per_window=*/1);

    h.Add(1);
    std::this_thread::sleep_for(std::chrono::milliseconds(3));
    h.Add(2);  // should advance to next window.

    // Cross max_span (4ms) relative to the current window start.
    // max_span = 4ms here.
    std::this_thread::sleep_for(std::chrono::milliseconds(7));
    h.Add(3);  // should reset.

    HistogramStats s = h.GetStats();
    EXPECT_EQ(s.count, 1);
    EXPECT_DOUBLE_EQ(s.min, 3);
    EXPECT_DOUBLE_EQ(s.max, 3);
    EXPECT_DOUBLE_EQ(s.sum, 3);
}

TEST(HistogramWindowingImplTest, TestCloneConsistencyAndIndependence) {
    // Use a large window to avoid relying on wall clock and window advancement.
    HistogramWindowingImpl h(/*num_windows=*/4, /*micros_per_window=*/60ULL * 1000ULL * 1000ULL,
                             /*min_num_per_window=*/1);
    h.Add(1);
    h.Add(2);
    h.Add(3);
    h.Add(4);

    const HistogramStats before = h.GetStats();

    auto cloned_base = h.Clone();
    auto cloned = std::dynamic_pointer_cast<HistogramWindowingImpl>(cloned_base);
    ASSERT_TRUE(cloned != nullptr);

    const HistogramStats cloned_before = cloned->GetStats();
    EXPECT_EQ(cloned_before.count, before.count);
    EXPECT_DOUBLE_EQ(cloned_before.sum, before.sum);
    EXPECT_DOUBLE_EQ(cloned_before.min, before.min);
    EXPECT_DOUBLE_EQ(cloned_before.max, before.max);
    EXPECT_DOUBLE_EQ(cloned_before.average, before.average);
    EXPECT_DOUBLE_EQ(cloned_before.stddev, before.stddev);
    EXPECT_DOUBLE_EQ(cloned_before.p50, before.p50);
    EXPECT_DOUBLE_EQ(cloned_before.p90, before.p90);
    EXPECT_DOUBLE_EQ(cloned_before.p95, before.p95);
    EXPECT_DOUBLE_EQ(cloned_before.p99, before.p99);
    EXPECT_DOUBLE_EQ(cloned_before.p999, before.p999);

    // Mutating original should not affect cloned.
    h.Add(100);
    const HistogramStats after_original = h.GetStats();
    const HistogramStats after_clone = cloned->GetStats();
    EXPECT_EQ(after_clone.count, cloned_before.count);
    EXPECT_EQ(after_original.count, cloned_before.count + 1);

    // Mutating cloned should not affect original.
    cloned->Add(200);
    const HistogramStats after_clone2 = cloned->GetStats();
    const HistogramStats after_original2 = h.GetStats();
    EXPECT_EQ(after_original2.count, after_original.count);
    EXPECT_EQ(after_clone2.count, cloned_before.count + 1);
}

}  // namespace paimon::test
