#include "origincar_base/log.hpp"

#include <chrono>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <unistd.h>

namespace
{
using Clock = std::chrono::steady_clock;

void require(bool condition, const std::string &message)
{
  if (!condition)
  {
    throw std::runtime_error(message);
  }
}

void requireContains(const std::string &text, const std::string &expected, const std::string &message)
{
  if (text.find(expected) == std::string::npos)
  {
    throw std::runtime_error(message + " missing=" + expected + " text=" + text);
  }
}

std::string readFile(const std::string &path)
{
  std::ifstream input(path.c_str());
  return std::string((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
}

std::string makeTempDir()
{
  const std::string path = "/tmp/origincar_timing_logger_test_" + std::to_string(getpid());
  std::string command = "rm -rf " + path + " && mkdir -p " + path;
  require(std::system(command.c_str()) == 0, "create temp log dir");
  return path;
}

void testFinishedFramesAreWrittenImmediatelyAndStatsAccumulate()
{
  const auto root = makeTempDir();
  const auto start = Clock::time_point(std::chrono::seconds(0));
  origincar_base_logging::ScanOdomTimingLogger logger("origincar_base", root, start);

  const auto first = logger.startFrame(start);
  require(logger.finishFrame(first, start + std::chrono::milliseconds(15)), "finish first frame");
  std::string contents = readFile(logger.logPath());
  requireContains(contents, "frame_id=1", "first frame id");
  requireContains(contents, "duration_ms=15.000", "first frame duration");
  requireContains(contents, "avg_ms=15.000", "first frame average");
  requireContains(contents, "max_ms=15.000", "first frame max");
  requireContains(contents, "min_ms=15.000", "first frame min");

  const auto second = logger.startFrame(start + std::chrono::milliseconds(20));
  require(logger.finishFrame(second, start + std::chrono::milliseconds(50)), "finish second frame");
  contents = readFile(logger.logPath());
  requireContains(contents, "frame_id=2", "second frame id");
  requireContains(contents, "duration_ms=30.000", "second frame duration");
  requireContains(contents, "avg_ms=22.500", "second frame average");
  requireContains(contents, "max_ms=30.000", "second frame max");
  requireContains(contents, "min_ms=15.000", "second frame min");
}

void testSummaryPrintIsThrottledToOneHz()
{
  const auto root = makeTempDir();
  const auto start = Clock::time_point(std::chrono::seconds(0));
  origincar_base_logging::ScanOdomTimingLogger logger("origincar_base", root, start);
  std::string summary;

  const auto frame = logger.startFrame(start);
  require(logger.finishFrame(frame, start + std::chrono::milliseconds(10)), "finish frame");
  require(!logger.makeSummaryLineIfDue(start + std::chrono::milliseconds(999), &summary),
          "summary should not be due before one second");
  require(logger.makeSummaryLineIfDue(start + std::chrono::milliseconds(1000), &summary),
          "summary should be due at one second");
  requireContains(summary, "frames=1", "summary frame count");
  requireContains(summary, "avg_ms=10.000", "summary average");
  require(!logger.makeSummaryLineIfDue(start + std::chrono::milliseconds(1500), &summary),
          "summary should be throttled after printing");
}

void testTerminalOutputDefaultsToOff()
{
  const auto root = makeTempDir();
  origincar_base_logging::ScanOdomTimingLogger logger("origincar_base", root);
  require(!logger.printToTerminal(), "terminal output should default to off");
  logger.setPrintToTerminal(true);
  require(logger.printToTerminal(), "terminal output should be enabled");
}
}  // namespace

int main()
{
  try
  {
    testFinishedFramesAreWrittenImmediatelyAndStatsAccumulate();
    testSummaryPrintIsThrottledToOneHz();
    testTerminalOutputDefaultsToOff();
  }
  catch (const std::exception &error)
  {
    std::cerr << "scan_odom_timing_logger_test failed: " << error.what() << std::endl;
    return 1;
  }
  return 0;
}
