#include <gtest/gtest.h>

#include <algorithm>
#include <array>
#include <limits>
#include <string_view>
#include <utility>

#include <fesa/results/result_database.hpp>

namespace {

std::array<double, 6> zeros() {
    return {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
}

fesa::ResultDatabase valid_database() {
    fesa::NodalFrame nodal{
        {fesa::NodeId{0}, fesa::NodeId{1}},
        {
            zeros(),
            {1.0, 2.0, 3.0, 0.1, 0.2, 0.3},
        },
        {
            {-10.0, -20.0, -30.0, -1.0, -2.0, -3.0},
            zeros(),
        },
    };
    fesa::ResultFrame frame{1.0, std::move(nodal), {}};
    fesa::ResultStep step{"Load", {std::move(frame)}};
    return {"1.0.0", {std::move(step)}};
}

bool has_diagnostic(
    const fesa::Status& status,
    const std::string_view code) {
    return std::ranges::any_of(
        status.diagnostics,
        [code](const fesa::Diagnostic& diagnostic) {
            return diagnostic.code == code &&
                   diagnostic.stage == fesa::DiagnosticStage::results &&
                   diagnostic.severity == fesa::Severity::error;
        });
}

TEST(NodalFrame, AcceptsFiniteSixComponentFieldsWithMatchingNodeIds) {
    const auto database = valid_database();

    const auto status = fesa::validate_result_database(database);

    EXPECT_TRUE(status.succeeded);
    EXPECT_TRUE(status.diagnostics.empty());
}

TEST(NodalFrame, RejectsDisplacementSizeMismatch) {
    auto database = valid_database();
    database.steps[0].frames[0].nodal.displacement.pop_back();

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.nodal_size_mismatch"));
}

TEST(NodalFrame, RejectsReactionSizeMismatch) {
    auto database = valid_database();
    database.steps[0].frames[0].nodal.reaction.pop_back();

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.nodal_size_mismatch"));
}

TEST(NodalFrame, RejectsDuplicateNodeId) {
    auto database = valid_database();
    database.steps[0].frames[0].nodal.node_ids[1] = fesa::NodeId{0};

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.duplicate_node_id"));
}

TEST(NodalFrame, RejectsNonfiniteDisplacement) {
    auto database = valid_database();
    database.steps[0].frames[0].nodal.displacement[1][2] =
        std::numeric_limits<double>::quiet_NaN();

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value"));
}

TEST(NodalFrame, RejectsNonfiniteReaction) {
    auto database = valid_database();
    database.steps[0].frames[0].nodal.reaction[0][4] =
        std::numeric_limits<double>::infinity();

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value"));
}

TEST(ResultDatabase, RejectsDuplicateStepName) {
    auto database = valid_database();
    database.steps.push_back(database.steps[0]);

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.duplicate_step_name"));
}

TEST(ResultDatabase, RejectsDuplicateFrameTimeWithinStep) {
    auto database = valid_database();
    database.steps[0].frames.push_back(database.steps[0].frames[0]);

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.duplicate_frame_time"));
}

TEST(ResultDatabase, RejectsNonfiniteFrameTime) {
    auto database = valid_database();
    database.steps[0].frames[0].step_time =
        std::numeric_limits<double>::quiet_NaN();

    const auto status = fesa::validate_result_database(database);

    EXPECT_FALSE(status.succeeded);
    EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value"));
}

}  // namespace
