#include <array>
#include <fstream>
#include <iostream>
#include <nntpchan/base64.hpp>
#include <nntpchan/crypto.hpp>
#include <nntpchan/nntp_auth.hpp>

namespace nntpchan
{
HashedCredDB::HashedCredDB() : LineReader() {}

bool HashedCredDB::CheckLogin(const std::string &user, const std::string &passwd)
{
  std::unique_lock<std::mutex> lock(m_access);
  m_found = false;
  m_user = user;
  m_passwd = passwd;
  m_instream->seekg(0, std::ios::end);
  const auto l = m_instream->tellg();
  m_instream->seekg(0, std::ios::beg);
  char *buff = new char[l];
  // read file
  m_instream->read(buff, l);
  Data(buff, l);
  delete[] buff;
  return m_found;
}

bool HashedCredDB::ProcessLine(const std::string &line)
{
  // strip comments
  auto comment = line.find("#");
  std::string part = line;
  for (; comment != std::string::npos; comment = part.find("#"))
  {
    if (comment)
      part = part.substr(0, comment);
    else
      break;
  }
  if (!part.size())
    return false; // empty line after comments
  auto idx = part.find(":");
  if (idx == std::string::npos)
    return false; // bad format
  if (m_user != part.substr(0, idx))
    return false; // username mismatch
  part = part.substr(idx + 1);

  idx = part.find(":");
  if (idx == std::string::npos)
    return false; // bad format
  std::string cred = part.substr(0, idx);
  std::string salt = part.substr(idx + 1);
  return Hash(m_passwd, salt) == cred;
}

void HashedCredDB::HandleLine(const std::string line)
{
  if (m_found)
    return;
  if (ProcessLine(line))
    m_found = true;
}

void HashedCredDB::SetStream(std::istream *s) { m_instream = s; }

std::string HashedCredDB::Hash(const std::string &data, const std::string &salt)
{
  SHA512Digest h;
  std::string d = data + salt;
  SHA512((const uint8_t *)d.c_str(), d.size(), h);
  return B64Encode(h.data(), h.size());
}

HashedFileDB::HashedFileDB(const std::string &fname) : m_fname(fname), f(nullptr) {}

HashedFileDB::~HashedFileDB() {}

void HashedFileDB::Close()
{
  if (f.is_open())
    f.close();
}

bool HashedFileDB::Open()
{
  if (!f.is_open())
    f.open(m_fname);
  if (f.is_open())
  {
    SetStream(&f);
    return true;
  }
  return false;
}
}