/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <chrono>
#include <cstdio>
#include <ctime>

#include "lib.rs.h"
#include "opendal.hpp"
#include "utils/ffi_converter.hpp"
#include "utils/rust_converter.hpp"

namespace opendal {

std::optional<std::string> parse_optional_string(ffi::OptionalString &&s) {
  if (s.has_value) {
    return std::string(std::move(s.value));
  } else {
    return std::nullopt;
  }
}

std::optional<bool> parse_optional_bool(ffi::OptionalBool &&b) {
  if (b.has_value) {
    return b.value;
  } else {
    return std::nullopt;
  }
}

Metadata parse_meta_data(ffi::Metadata &&meta) {
  Metadata metadata;

  // Basic information
  metadata.type = static_cast<EntryMode>(meta.mode);
  metadata.content_length = meta.content_length;

  // HTTP-style headers
  metadata.cache_control = parse_optional_string(std::move(meta.cache_control));
  metadata.content_disposition =
      parse_optional_string(std::move(meta.content_disposition));
  metadata.content_md5 = parse_optional_string(std::move(meta.content_md5));
  metadata.content_type = parse_optional_string(std::move(meta.content_type));
  metadata.content_encoding =
      parse_optional_string(std::move(meta.content_encoding));
  metadata.etag = parse_optional_string(std::move(meta.etag));

  // Versioning information
  metadata.version = parse_optional_string(std::move(meta.version));
  metadata.is_current = parse_optional_bool(std::move(meta.is_current));
  metadata.is_deleted = meta.is_deleted;

  // Parse last_modified timestamp
  auto last_modified_str = parse_optional_string(std::move(meta.last_modified));
  if (last_modified_str.has_value()) {
    // Parse ISO 8601 string to time_point using strptime to avoid locale lock
    std::tm tm = {};
    const char *str = last_modified_str.value().c_str();

    // Parse ISO 8601 format: YYYY-MM-DDTHH:MM:SS
    int year, month, day, hour, minute, second;
    if (sscanf(str, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &minute,
               &second) == 6) {
      tm.tm_year = year - 1900;  // years since 1900
      tm.tm_mon = month - 1;     // months since January (0-11)
      tm.tm_mday = day;
      tm.tm_hour = hour;
      tm.tm_min = minute;
      tm.tm_sec = second;
      tm.tm_isdst = -1;  // let mktime determine DST

      std::time_t time_t_value = std::mktime(&tm);
      if (time_t_value != -1) {
        metadata.last_modified =
            std::chrono::system_clock::from_time_t(time_t_value);
      }
    }
  }

  return metadata;
}

Operator::Operator() noexcept = default;

void Operator::Destroy() noexcept {
  if (operator_) {
    ffi::delete_operator(operator_);
    operator_ = nullptr;
  }
}

Operator::Operator(std::string_view scheme,
                   const std::unordered_map<std::string, std::string> &config) {
  auto rust_map = rust::Vec<ffi::HashMapValue>();
  rust_map.reserve(config.size());

  for (auto &[k, v] : config) {
    rust_map.push_back({utils::rust_string(k), utils::rust_string(v)});
  }

  operator_ = ffi::new_operator(utils::rust_str(scheme), rust_map);
}

Operator::~Operator() noexcept { Destroy(); }

Operator::Operator(Operator &&other) noexcept : operator_(other.operator_) {
  other.operator_ = nullptr;
}

Operator &Operator::operator=(Operator &&other) noexcept {
  if (this != &other) {
    Destroy();

    operator_ = other.operator_;
    other.operator_ = nullptr;
  }

  return *this;
}

bool Operator::Available() const { return operator_ != nullptr; }

// We can't avoid copy, because std::vector hides the internal structure.
// std::vector doesn't support init from a pointer without copy.
std::string Operator::Read(std::string_view path) {
  auto rust_vec = operator_->read(utils::rust_str(path));
  return {rust_vec.begin(), rust_vec.end()};
}

void Operator::Write(std::string_view path, std::string_view data) {
  rust::Vec<uint8_t> vec;
  std::copy(data.begin(), data.end(), std::back_inserter(vec));
  operator_->write(utils::rust_str(path), vec);
}

bool Operator::Exists(std::string_view path) {
  return operator_->exists(utils::rust_str(path));
}

bool Operator::IsExist(std::string_view path) { return Exists(path); }

void Operator::CreateDir(std::string_view path) {
  operator_->create_dir(utils::rust_str(path));
}

void Operator::Copy(std::string_view src, std::string_view dst) {
  operator_->copy(utils::rust_str(src), utils::rust_str(dst));
}

void Operator::Rename(std::string_view src, std::string_view dst) {
  operator_->rename(utils::rust_str(src), utils::rust_str(dst));
}

void Operator::Remove(std::string_view path) {
  operator_->remove(utils::rust_str(path));
}

Metadata Operator::Stat(std::string_view path) {
  return parse_meta_data(operator_->stat(utils::rust_str(path)));
}

std::vector<Entry> Operator::List(std::string_view path) {
  auto rust_vec = operator_->list(utils::rust_str(path));

  std::vector<Entry> entries;
  entries.reserve(rust_vec.size());
  for (auto &&entry : rust_vec) {
    entries.emplace_back(utils::parse_entry(std::move(entry)));
  }

  return entries;
}

Lister Operator::GetLister(std::string_view path) {
  return operator_->lister(utils::rust_str(path));
}

Reader Operator::GetReader(std::string_view path) {
  return operator_->reader(utils::rust_str(path));
}

}  // namespace opendal
opendal::Capability opendal::Operator::Info() {
  auto op_info = operator_->info();
  return Capability{
      .stat = op_info.stat,
      .stat_with_if_match = op_info.stat_with_if_match,
      .stat_with_if_none_match = op_info.stat_with_if_none_match,
      .read = op_info.read,
      .read_with_if_match = op_info.read_with_if_match,
      .read_with_if_none_match = op_info.read_with_if_none_match,
      .read_with_override_cache_control = op_info.read_with_override_cache_control,
      .read_with_override_content_disposition =
          op_info.read_with_override_content_disposition,
      .read_with_override_content_type = op_info.read_with_override_content_type,
      .write = op_info.write,
      .write_can_multi = op_info.write_can_multi,
      .write_can_empty = op_info.write_can_empty,
      .write_can_append = op_info.write_can_append,
      .write_with_content_type = op_info.write_with_content_type,
      .write_with_content_disposition = op_info.write_with_content_disposition,
      .write_with_cache_control = op_info.write_with_cache_control,
      .write_multi_max_size = op_info.write_multi_max_size,
      .write_multi_min_size = op_info.write_multi_min_size,
      .write_total_max_size = op_info.write_total_max_size,
      .create_dir = op_info.create_dir,
      .delete_feature = op_info.delete_feature,
      .copy = op_info.copy,
      .rename = op_info.rename,
      .list = op_info.list,
      .list_with_limit = op_info.list_with_limit,
      .list_with_start_after = op_info.list_with_start_after,
      .list_with_recursive = op_info.list_with_recursive,
      .presign = op_info.presign,
      .presign_read = op_info.presign_read,
      .presign_stat = op_info.presign_stat,
      .presign_write = op_info.presign_write,
      .shared = op_info.shared,
  };
}
