// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright 2024-2025 Max-Planck-Institut für Nachhaltige Materialien GmbH
// Copyright 2011-2024 Max-Planck-Institut für Eisenforschung GmbH
//
// DAMASK is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
/**
 * @file CLI.h
 * @brief DAMASK C++ unittesting fixtures
 *
 * @author Daniel Otto de Mentock, Max‑Planck‑Institut für Nachhaltige Materialien GmbH
 * @author Martin Diehl, KU Leuven
 * @copyright
 *   Max‑Planck‑Institut für Nachhaltige Materialien GmbH
 */

#pragma once

#include <ISO_Fortran_binding.h>

#include <cstdlib>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <vector>
#include <unistd.h>

#include "../../src/IO.h"

// Mock fortran functions (resolve directly so the actual implementations are not pulled from fortran)
namespace {
struct FIOErrorCalled final {};
} // namespace

static std::string last_f_io_error_msg_storage;

inline std::string& last_f_io_error_msg() {
  return last_f_io_error_msg_storage;
}

extern "C" {
static std::vector<std::string> fortran_mock_buffer;

void F_IO_error(int, CFI_cdesc_t* msg) {
  last_f_io_error_msg() = msg ? std::string(static_cast<char*>(msg->base_addr), msg->elem_len) : "";
  throw FIOErrorCalled{};
}
void F_IO_print(CFI_cdesc_t* c_str) {
  fortran_mock_buffer.emplace_back(c_str ? std::string(static_cast<char*>(c_str->base_addr), c_str->elem_len) : "");
}
void F_printCompileOptions() {}
bool IO_redirectedSTDOUT = false;
bool IO_redirectedSTDERR = false;
}

static bool io_error_called = false;
static int io_error_id = 0;
static std::string io_error_msg;

static void mock_io_error(int error_ID, CFI_cdesc_t* msg) {
  io_error_called = true;
  io_error_id = error_ID;
  std::string msg_str = msg ? std::string(static_cast<char*>(msg->base_addr), msg->elem_len) : "";
  io_error_msg = msg_str;
  last_f_io_error_msg() = msg_str;
  throw FIOErrorCalled{};
}

// Use object lifespan to ensure clean fortran buffer across tests
struct FortranBufferGuard {
  FortranBufferGuard() {
    fortran_mock_buffer.clear();
    last_f_io_error_msg().clear();
  }
};

// Use object lifespan to ensure IO mocking across tests
struct IOMockGuard {
  decltype(IO::fn) old_fn;
  IOMockGuard() : old_fn(IO::fn) {
    IO::fn = &mock_io_error;
    io_error_called = false;
    io_error_id = 0;
    io_error_msg.clear();
  }
  ~IOMockGuard() {
    IO::fn = old_fn;
  }
};

struct TempDirGuard {
  explicit TempDirGuard(const std::string& prefix) : path(std::filesystem::temp_directory_path() / (prefix + ".XXXXXX")) {
    std::string buffer;
    buffer = path.string();
    buffer.push_back('\0');
    const char* created = mkdtemp(buffer.data());
    if (created == nullptr) {
      throw std::runtime_error("mkdtemp failed for " + path.string());
    }
    path = created;
  }

  ~TempDirGuard() {
    std::error_code ec;
    std::filesystem::remove_all(path, ec);
  }

  std::filesystem::path path;
};
