#include <chrono>
#include <cmath>
#include <ctime>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <type_traits>

namespace
{
    namespace fs = std::filesystem;

    struct ClockAnchor
    {
        fs::file_time_type fsNow;
        std::chrono::system_clock::time_point sysNow;
    };

    ClockAnchor makeClockAnchor()
    {
        using namespace std::chrono;

        while (true)
        {
            const auto sysBefore = system_clock::now();
            const auto fsRawNow = fs::file_time_type::clock::now();
            const auto sysAfter = system_clock::now();

            const auto sysBeforeRoundedSeconds = floor<seconds>(sysBefore);
            const auto sysAfterRoundedSeconds = floor<seconds>(sysAfter);
            const auto fsRoundedSeconds = floor<seconds>(fsRawNow);

            if (sysBeforeRoundedSeconds == sysAfterRoundedSeconds)
            {
                return {fs::file_time_type{fsRoundedSeconds.time_since_epoch()},
                        system_clock::time_point{sysBeforeRoundedSeconds.time_since_epoch()}};
            }
        }
    }

    ClockAnchor makeRawClockAnchor()
    {
        return {fs::file_time_type::clock::now(), std::chrono::system_clock::now()};
    }

    fs::file_time_type systemToFileTime(std::time_t value, const ClockAnchor& anchor)
    {
        using namespace std::chrono;
        const auto sysTime = system_clock::from_time_t(value);
        return time_point_cast<fs::file_time_type::duration>(sysTime - anchor.sysNow + anchor.fsNow);
    }

    std::time_t fileTimeToSystem(const fs::file_time_type& value, const ClockAnchor& anchor)
    {
        using namespace std::chrono;
        const auto sctp = time_point_cast<system_clock::duration>(value - anchor.fsNow + anchor.sysNow);
        return system_clock::to_time_t(sctp);
    }

    std::string formatTime(std::time_t value)
    {
        std::tm timeInfo{};
        localtime_r(&value, &timeInfo);
        std::ostringstream output;
        output << std::put_time(&timeInfo, "%F %T");
        return output.str();
    }

    long long fsSecondsFromEpoch(const fs::file_time_type& value)
    {
        return std::chrono::duration_cast<std::chrono::seconds>(value.time_since_epoch()).count();
    }

    long long fsNanosecondsFromEpoch(const fs::file_time_type& value)
    {
        return std::chrono::duration_cast<std::chrono::nanoseconds>(value.time_since_epoch()).count();
    }

    std::string formatSecondsFractionFromNanoseconds(long long totalNanoseconds)
    {
        constexpr long long nanosPerSecond = 1000000000LL;
        long long seconds = totalNanoseconds / nanosPerSecond;
        long long nanoseconds = totalNanoseconds % nanosPerSecond;

        if (nanoseconds < 0)
        {
            nanoseconds += nanosPerSecond;
            --seconds;
        }

        std::ostringstream output;
        output << seconds << '.' << std::setw(9) << std::setfill('0') << nanoseconds
               << std::setfill(' ');
        return output.str();
    }

    long long anchorDeltaNanoseconds(const ClockAnchor& anchor)
    {
        using namespace std::chrono;
        const auto fsAsSystemDuration =
            duration_cast<system_clock::duration>(anchor.fsNow.time_since_epoch());
        const auto delta = fsAsSystemDuration - anchor.sysNow.time_since_epoch();
        return duration_cast<nanoseconds>(delta).count();
    }

    long long fsNanosecondsDiff(const fs::file_time_type& left, const fs::file_time_type& right)
    {
        return std::llabs(
            std::chrono::duration_cast<std::chrono::nanoseconds>(left - right).count());
    }

    long long expectedFsRoundTripDeltaNs(const fs::file_time_type& fsStart, const ClockAnchor& anchor)
    {
        using namespace std::chrono;
        const auto preciseSystemFromFs =
            time_point_cast<system_clock::duration>(fsStart - anchor.fsNow + anchor.sysNow);
        const auto roundedSystemFromFs = system_clock::from_time_t(system_clock::to_time_t(preciseSystemFromFs));
        return std::llabs(duration_cast<nanoseconds>(preciseSystemFromFs - roundedSystemFromFs).count());
    }

    void printAnchorDetails(const std::string& label, const ClockAnchor& anchor)
    {
        const auto sysNanoseconds =
            std::chrono::duration_cast<std::chrono::nanoseconds>(anchor.sysNow.time_since_epoch()).count();
        const auto deltaNanoseconds = anchorDeltaNanoseconds(anchor);

        std::cout << "\n[A] " << label << std::endl;
        std::cout << "  systemNow   : " << formatSecondsFractionFromNanoseconds(sysNanoseconds)
                  << std::endl;
        std::cout << "  fsNow       : "
                  << formatSecondsFractionFromNanoseconds(fsNanosecondsFromEpoch(anchor.fsNow))
                  << std::endl;
        std::cout << "  delta(FS-S) : " << formatSecondsFractionFromNanoseconds(deltaNanoseconds)
                  << " s (" << deltaNanoseconds << " ns)" << std::endl;
    }

    struct TimeComponents
    {
        int year;
        int month;
        int day;
        int hour;
        int minute;
        int second;
    };

    std::time_t makeSystemTimeFromComponents(const TimeComponents& components)
    {
        std::tm timeInfo{};
        timeInfo.tm_year = components.year - 1900;
        timeInfo.tm_mon = components.month - 1;
        timeInfo.tm_mday = components.day;
        timeInfo.tm_hour = components.hour;
        timeInfo.tm_min = components.minute;
        timeInfo.tm_sec = components.second;
        timeInfo.tm_isdst = -1;
        return std::mktime(&timeInfo);
    }

    void printClockTypeComparison()
    {
        using FsDuration = fs::file_time_type::duration;
        using SysDuration = std::chrono::system_clock::duration;

        const bool sameDuration = std::is_same<FsDuration, SysDuration>::value;
        const bool sameRep = std::is_same<FsDuration::rep, SysDuration::rep>::value;
        const bool samePeriod = std::is_same<FsDuration::period, SysDuration::period>::value;

        std::cout << "\n[0] Comparaison des types C++17" << std::endl;
        std::cout << "  duration    : " << (sameDuration ? "identique" : "different")
                  << std::endl;
        std::cout << "  rep         : " << (sameRep ? "identique" : "different") << std::endl;
        std::cout << "  period      : " << (samePeriod ? "identique" : "different")
                  << std::endl;
    }
} // namespace

int main()
{
    const auto stableAnchor = makeClockAnchor();
    const auto rawAnchor = makeRawClockAnchor();

    std::cout << "POC conversion horloge systeme <-> horloge filesystem" << std::endl;
    std::cout << "====================================================" << std::endl;

    printClockTypeComparison();
    printAnchorDetails("ancre stabilisee", stableAnchor);
    printAnchorDetails("ancre brute", rawAnchor);

    const std::time_t systemStart = std::time(nullptr);
    const auto fsFromSystem = systemToFileTime(systemStart, stableAnchor);
    const std::time_t systemBack = fileTimeToSystem(fsFromSystem, stableAnchor);

    std::cout << "\n[1] Systeme -> FS -> Systeme" << std::endl;
    std::cout << "  systemStart : " << systemStart << " (" << formatTime(systemStart) << ')'
              << std::endl;
    std::cout << "  fsFromSystem: " << fsSecondsFromEpoch(fsFromSystem)
              << " (secondes depuis epoch FS)" << std::endl;
    std::cout << "  systemBack  : " << systemBack << " (" << formatTime(systemBack) << ')'
              << std::endl;
    std::cout << "  validation  : "
              << (systemBack == systemStart ? "OK (identique)" : "ECHEC (different)")
              << std::endl;

    const auto fsStart = stableAnchor.fsNow + std::chrono::hours(12) + std::chrono::milliseconds(789);
    const std::time_t systemFromFs = fileTimeToSystem(fsStart, stableAnchor);
    const auto fsBack = systemToFileTime(systemFromFs, stableAnchor);
    const auto fsRoundTripDeltaNs = fsNanosecondsDiff(fsBack, fsStart);
    const auto fsExpectedRoundTripDeltaNs = expectedFsRoundTripDeltaNs(fsStart, stableAnchor);
    const bool fsRoundTripExactMatch = (fsRoundTripDeltaNs == fsExpectedRoundTripDeltaNs);

    std::cout << "\n[2] FS -> Systeme -> FS" << std::endl;
    std::cout << "  fsStart     : "
              << formatSecondsFractionFromNanoseconds(fsNanosecondsFromEpoch(fsStart))
              << std::endl;
    std::cout << "  systemFromFs: " << systemFromFs << " (" << formatTime(systemFromFs) << ')'
              << std::endl;
    std::cout << "  fsBack      : "
              << formatSecondsFractionFromNanoseconds(fsNanosecondsFromEpoch(fsBack))
              << std::endl;
    std::cout << "  delta       : " << formatSecondsFractionFromNanoseconds(fsRoundTripDeltaNs)
              << " s (" << fsRoundTripDeltaNs << " ns)" << std::endl;
    std::cout << "  attendu     : " << formatSecondsFractionFromNanoseconds(fsExpectedRoundTripDeltaNs)
              << " s (" << fsExpectedRoundTripDeltaNs << " ns)" << std::endl;
    std::cout << "  validation  : "
              << (fsRoundTripExactMatch ? "OK (exactement attendu)" : "ECHEC (delta inattendu)")
              << std::endl;

    const auto fsStartRaw = rawAnchor.fsNow + std::chrono::hours(12) + std::chrono::milliseconds(789);
    const std::time_t systemFromFsRaw = fileTimeToSystem(fsStartRaw, rawAnchor);
    const auto fsBackRaw = systemToFileTime(systemFromFsRaw, rawAnchor);
    const auto fsRoundTripDeltaRawNs = fsNanosecondsDiff(fsBackRaw, fsStartRaw);
    const auto fsExpectedRoundTripDeltaRawNs = expectedFsRoundTripDeltaNs(fsStartRaw, rawAnchor);

    std::cout << "\n[2b] FS -> Systeme -> FS (ancre non stabilisee)" << std::endl;
    std::cout << "  fsStartRaw  : "
              << formatSecondsFractionFromNanoseconds(fsNanosecondsFromEpoch(fsStartRaw))
              << std::endl;
    std::cout << "  systemRaw   : " << systemFromFsRaw << " (" << formatTime(systemFromFsRaw) << ')'
              << std::endl;
    std::cout << "  fsBackRaw   : "
              << formatSecondsFractionFromNanoseconds(fsNanosecondsFromEpoch(fsBackRaw))
              << std::endl;
    std::cout << "  deltaRaw    : " << formatSecondsFractionFromNanoseconds(fsRoundTripDeltaRawNs)
              << " s (" << fsRoundTripDeltaRawNs << " ns)" << std::endl;
    std::cout << "  attenduRaw  : " << formatSecondsFractionFromNanoseconds(fsExpectedRoundTripDeltaRawNs)
              << " s (" << fsExpectedRoundTripDeltaRawNs << " ns)" << std::endl;
    std::cout << "  comparaison : "
              << (fsRoundTripDeltaRawNs == fsExpectedRoundTripDeltaRawNs ? "OK (coherent)"
                                                                          : "ECHEC (incoherent)")
              << std::endl;

    const TimeComponents components{2026, 8, 6, 21, 10, 0};
    const std::time_t systemFromComponents = makeSystemTimeFromComponents(components);
    const auto fsFromComponents = systemToFileTime(systemFromComponents, stableAnchor);
    const std::time_t systemFromComponentsBack = fileTimeToSystem(fsFromComponents, stableAnchor);

    std::cout << "\n[3] Composantes -> Systeme -> FS -> Systeme" << std::endl;
    std::cout << "  composantes : " << components.year << '-' << std::setw(2) << std::setfill('0')
              << components.month << '-' << std::setw(2) << std::setfill('0') << components.day
              << ' ' << std::setw(2) << std::setfill('0') << components.hour << ':' << std::setw(2)
              << std::setfill('0') << components.minute << ':' << std::setw(2)
              << std::setfill('0') << components.second << std::setfill(' ') << std::endl;
    std::cout << "  systemTime  : " << systemFromComponents << " ("
              << formatTime(systemFromComponents) << ')' << std::endl;
    std::cout << "  fsFromComp  : " << fsSecondsFromEpoch(fsFromComponents)
              << " (secondes depuis epoch FS)" << std::endl;
    std::cout << "  systemBack  : " << systemFromComponentsBack << " ("
              << formatTime(systemFromComponentsBack) << ')' << std::endl;
    std::cout << "  validation  : "
              << (systemFromComponentsBack == systemFromComponents ? "OK (identique)"
                                                                   : "ECHEC (different)")
              << std::endl;

    return (systemBack == systemStart && fsRoundTripExactMatch
            && systemFromComponentsBack == systemFromComponents)
               ? 0
               : 1;
}