#include <fesa/results/result_database.hpp>

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <set>
#include <string>
#include <utility>

namespace fesa {
namespace {

void add_error(
    std::vector<Diagnostic>& diagnostics,
    std::string code,
    std::string message) {
    diagnostics.push_back({
        DiagnosticStage::results,
        Severity::error,
        std::move(code),
        std::move(message),
        std::nullopt,
    });
}

bool is_finite(const std::array<double, 6>& field) {
    return std::ranges::all_of(
        field,
        [](const double component) {
            return std::isfinite(component);
        });
}

void validate_nodal_frame(
    const NodalFrame& nodal,
    std::vector<Diagnostic>& diagnostics) {
    if (
        nodal.displacement.size() != nodal.node_ids.size() ||
        nodal.reaction.size() != nodal.node_ids.size()) {
        add_error(
            diagnostics,
            "results.nodal_size_mismatch",
            "Nodal IDs, displacement, and reaction fields must have "
            "matching sizes.");
    }
    if (nodal.origins.size() != nodal.node_ids.size()) {
        add_error(
            diagnostics,
            "results.nodal_origin_size_mismatch",
            "Nodal IDs and origins must have matching sizes.");
    }

    std::set<std::int64_t> node_ids;
    for (const NodeId node_id : nodal.node_ids) {
        if (!node_ids.insert(node_id.value()).second) {
            add_error(
                diagnostics,
                "results.duplicate_node_id",
                "Nodal frame contains duplicate node ID " +
                    std::to_string(node_id.value()) + ".");
        }
    }

    for (const auto& displacement : nodal.displacement) {
        if (!is_finite(displacement)) {
            add_error(
                diagnostics,
                "results.nonfinite_value",
                "Nodal displacement contains a nonfinite component.");
        }
    }

    for (const auto& reaction : nodal.reaction) {
        if (!is_finite(reaction)) {
            add_error(
                diagnostics,
                "results.nonfinite_value",
                "Nodal reaction contains a nonfinite component.");
        }
    }
}

void validate_beam_section_result(
    const BeamSectionResult& result,
    std::vector<Diagnostic>& diagnostics) {
    if (
        !std::isfinite(result.xi) ||
        !is_finite(result.section_strain) ||
        !is_finite(result.section_force) ||
        !std::isfinite(result.centroid_sigma_xx) ||
        !std::ranges::all_of(
            result.sigma_xx,
            [](const double value) { return std::isfinite(value); })) {
        add_error(
            diagnostics,
            "results.nonfinite_value",
            "Beam section result contains a nonfinite value.");
    }
}

void validate_element_frame(
    const ElementFrame& element,
    const NodalFrame& nodal,
    std::vector<Diagnostic>& diagnostics) {
    std::set<std::int64_t> nodal_ids;
    for (const NodeId node_id : nodal.node_ids) {
        nodal_ids.insert(node_id.value());
    }

    std::set<std::int64_t> element_ids;
    for (const BeamElementFrame& beam : element.beams) {
        if (!element_ids.insert(beam.element.value()).second) {
            add_error(
                diagnostics,
                "results.duplicate_element_id",
                "Element frame contains duplicate element ID " +
                    std::to_string(beam.element.value()) + ".");
        }

        if (
            !is_finite(beam.local_frame.ex) ||
            !is_finite(beam.local_frame.ey) ||
            !is_finite(beam.local_frame.ez)) {
            add_error(
                diagnostics,
                "results.nonfinite_value",
                "Beam local frame contains a nonfinite component.");
        }

        const BeamSectionResult& first = beam.end_results[0];
        const BeamSectionResult& second = beam.end_results[1];
        if (first.end_node == second.end_node) {
            add_error(
                diagnostics,
                "results.duplicate_beam_end_node",
                "Beam element result contains the same node at both ends.");
        }
        if (
            first.xi != -1.0 || second.xi != 1.0 ||
            !nodal_ids.contains(first.end_node.value()) ||
            !nodal_ids.contains(second.end_node.value())) {
            add_error(
                diagnostics,
                "results.invalid_beam_connectivity",
                "Beam end results must follow (-1, +1) connectivity and "
                "reference nodes in the nodal frame.");
        }
        if (first.sigma_xx.size() != second.sigma_xx.size()) {
            add_error(
                diagnostics,
                "results.recovery_point_count_mismatch",
                "Beam end results must have matching recovery-point "
                "counts.");
        }

        validate_beam_section_result(first, diagnostics);
        validate_beam_section_result(second, diagnostics);
    }
}

}  // namespace

Status validate_result_database(const ResultDatabase& database) {
    std::vector<Diagnostic> diagnostics;
    std::set<std::string> step_names;

    for (const ResultStep& step : database.steps) {
        if (!step_names.insert(step.name).second) {
            add_error(
                diagnostics,
                "results.duplicate_step_name",
                "Result database contains duplicate step name '" +
                    step.name + "'.");
        }

        std::set<double> frame_times;
        for (const ResultFrame& frame : step.frames) {
            if (!std::isfinite(frame.step_time)) {
                add_error(
                    diagnostics,
                    "results.nonfinite_value",
                    "Result frame has a nonfinite step time.");
            } else if (!frame_times.insert(frame.step_time).second) {
                add_error(
                    diagnostics,
                    "results.duplicate_frame_time",
                    "Result step '" + step.name +
                        "' contains duplicate frame time " +
                        std::to_string(frame.step_time) + ".");
            }

            validate_nodal_frame(frame.nodal, diagnostics);
            validate_element_frame(
                frame.element, frame.nodal, diagnostics);
        }
    }

    return {diagnostics.empty(), std::move(diagnostics)};
}

}  // namespace fesa
