
#include <iostream>
#include <iomanip>
#include <string>
#include <pqxx/pqxx>

#include <libgen.h> // basename() needed$

#include "DbConn.hpp"

#include "my_assert.hpp"
#include "io.hpp"


using namespace std;

/*   DbConn::DbConn
(std::string dbms,
 std::string host_name,
 std::string port,
 std::string schema,
 std::string db_name,
 std::string user_name,
 std::string password
 ):m_schema(schema)
   ,m_uri(dbms + "://" + user_name + ":"+ password + "@" + host_name + ":" + port + "/" + db_name)
   ,m_conn(m_uri)
{
   LOG_trace(__func__);
   LOG_STREAM << "DB[" << m_schema << "]: " << m_uri << endl;

   // FIXME port must be uint16_t
   // FIXME add check: dbms must be postgresql otherwise error
};
*/

   DbConn::DbConn(std::string db_uri, std::string schema):m_schema(schema)
                                                          ,m_uri(db_uri)
                                                          ,m_conn(m_uri)
{
   LOG_trace(__func__);
   LOG_STREAM << "DB[" << m_schema << "]: " << m_uri << endl;
};


// VLKB tables

// FIXME table to be created under Schema m_schema
// "SELECT current_schema();"
// PostgreSQL will create tables into 'current schema':
// current schema is the first schema mentioned on the search_path:
// SHOW search_path();
// SELECT current_schema();



void DbConn::dbExecCmds(const vector<string> sqlCmds)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   try
   {
      for(string cmd : sqlCmds)
         txn.exec(cmd);
      txn.commit();
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
      throw;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
      throw;
   }
};



pqxx::result DbConn::dbExecQuery3(string query)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   try
   {
      pqxx::result r{txn.exec(query)};
      return r;
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
   }
   return pqxx::result{};
};



void DbConn::dbExecQuery(string query)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   try
   {
      pqxx::result r{txn.exec(query)};

      // column names
      for(/*pqxx::row::size_type*/size_t i=0; i<r.columns();i++) LOG_STREAM << r.column_name(i) << "\t";
      LOG_STREAM << endl;


      // Results can be accessed and iterated again.  Even after the connection
      // has been closed.
      for (auto row: r)
      {
         // Iterate over fields in a row.
         for (auto field: row) LOG_STREAM << field.c_str() << " ";
         LOG_STREAM << endl;
      }
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
   }
};


vector<string> DbConn::dbExecQuery2(string query)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   vector<string> rr;

   try
   {
      pqxx::result r{txn.exec(query)};

      // column names
      string columns;
      for(/*pqxx::row::size_type*/size_t i=0; i<r.columns();i++)
      {
         columns += string{r.column_name(i)} + " | ";
      }
      rr.push_back(columns);

      // Results can be accessed and iterated again.  Even after the connection
      // has been closed.
      for (auto row: r)
      {
         // Iterate over fields in a row.
         string one_row;
         for (auto field: row) one_row += to_string(field) + " | ";
         rr.push_back(one_row);
      }
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
   }

   return rr;
};





//-----------------------------------------//
// queries with specific results format    //
//-----------------------------------------//


Survey DbConn::querySurveyAttributes(int sid)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   Survey surv("surveys");

   std::string qstr(surv.qry_SELECT_Attribs(sid));

//   try
//   {
      pqxx::result r{txn.exec(qstr)};
      my_assert(1==r.size(), __FILE__, __LINE__, "survey SID[" + to_string(sid) + "] found " + to_string(r.size()) + " times");
      surv.handleResult(r);
/*   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
      return surv;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
      return surv;
   }
*/
   return surv;
};




// used in adCheck()
std::vector<std::string> DbConn::queryTableNames(void)
{
   LOG_trace(__func__);

   std::vector<std::string> tnames;

   pqxx::work txn{m_conn};

   std::string qstr(
         "SELECT t.table_name "
         "FROM information_schema.tables t "
         "WHERE t.table_schema = '" + m_schema + "'"
         "AND t.table_type = 'BASE TABLE' "
         "ORDER BY t.table_name");

   try
   {
      pqxx::result r{txn.exec(qstr)};

      // Results can be accessed and iterated again.  Even after the connection
      // has been closed.
      for (auto row: r)
      {
         // Iterate over fields in a row.
         for (auto field: row)
         {
            tnames.push_back(field.c_str());
         }
      }
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
      return tnames;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
      return tnames;
   }
   return tnames;
};



std::string DbConn::queryPubdid(int hid)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   // FIXME consider schema to go to search_path setting and avoid it to be in each query
   std::string qstr("SELECT pubdid,header_id FROM " + m_schema + ".headers WHERE header_id = "
         + std::to_string(hid));

   std::string pubdid;

   try
   {
      pqxx::result r{txn.exec(qstr)};

      // Results can be accessed and iterated again.  Even after the connection
      // has been closed.
      for (auto row: r)
      {
         LOG_STREAM << "Row: ";
         //         strcpy(pubdid, row["pubdid"].c_str());
         pubdid = row["pubdid"].as<std::string>();
         // Iterate over fields in a row.
         for (auto field: row) LOG_STREAM << field.c_str() << " ";
         LOG_STREAM << endl;
      }
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
      return pubdid;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
      return pubdid;
   }

   return pubdid;
};

int DbConn::queryMaxHid(void)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   std::string qstr("SELECT coalesce(max(header_id), 0) AS max FROM headers");

   LOG_STREAM << qstr << endl;

   int max_hid = -1; // coalesce(): if no value yet -> replaces NULL with 0 (so app starts to count from 1)

   try
   {
      pqxx::result r{txn.exec(qstr)};

      // should cycle ones only: SQL-MAX() returns only one number (even if it would occur in more rows)/ from PSQL doc
      for (auto row: r)
      {
         max_hid = row["max"].as<int>();
      }
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << "SQL error: " << e.what()  << endl;
      LOG_STREAM << "Query was: " << e.query() << endl;
      throw;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << "Error: " << e.what() << endl;
      throw;
   }

   return max_hid;
};


cut_db_params_t DbConn::queryCutParams(std::string pubdid)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   // FIXME schema to search_path setting
   std::string qstr("SELECT pubdid,filename,hdunum,rest_frequency,storage_path,header,velocity_fits_unit FROM  "
      + m_schema + ".headers INNER JOIN "
      + m_schema + ".surveys ON "+m_schema+".headers.survey_id = "+m_schema+".surveys.survey_id WHERE pubdid = '"+ pubdid + "'");

   cut_db_params_t cut_params;

   LOG_STREAM << qstr << endl;

   try
   {
      pqxx::result r{txn.exec(qstr)};

      // Results can be accessed and iterated again.
      // Even after the connection has been closed.

      // FIXME assert(rowcount == 1): exactly one row returned 

      LOG_STREAM << "RowsCount: " << r.size() << endl;
      for (auto row: r)
      {
         cut_params.header        = row["header"].as<std::string>();
         cut_params.filename      = row["filename"].as<std::string>();
         cut_params.hdunum        = row["hdunum"].as<unsigned int>();
         cut_params.storage_path  = row["storage_path"].as<std::string>();

         const double default_when_null = 0.0;
         cut_params.rest_frequency = row["rest_frequency"].as<double>(default_when_null);
         const string empty_str;
         cut_params.velocity_fits_unit = row["velocity_fits_unit"].as<std::string>(empty_str);

         // Iterate over fields in a row.
         //for (auto field: row) LOG_STREAM << field.c_str() << " "; LOG_STREAM << endl;
      }
   }
   catch (pqxx::sql_error const &e)
   {
      LOG_STREAM << __func__ << ": SQL error: " << e.what()  << endl;
      LOG_STREAM << __func__ << ": Query was: " << e.query() << endl;
      //return cut_params;
   }
   catch (std::exception const &e)
   {
      LOG_STREAM << __func__ << ": Error: " << e.what() << endl;
      //return cut_params;
   }

   return cut_params;
};



cut_db_params_t DbConn::BqueryCutParams(std::string pubdid)
{
   LOG_trace(__func__);

   pqxx::work txn{m_conn};

   /*/ FIXME schema to search_path setting
   std::string qstr("SELECT pubdid,filename,hdunum,rest_frequency,storage_path,header,velocity_fits_unit FROM  "
      + m_schema + ".headers INNER JOIN "
      + m_schema + ".surveys ON "+m_schema+".headers.survey_id = "+m_schema+".surveys.survey_id WHERE pubdid = '"+ pubdid + "'");
*/
   std::string qstr("SELECT access_url,rest_frequency,storage_path,velocity_fits_unit,name,species,transition FROM  "
      +m_schema+".obscore INNER JOIN "+ m_schema + ".surveys ON "
      +m_schema+".obscore.obs_collection = CONCAT("
         +m_schema+".surveys.name, ' ',"
         +m_schema+".surveys.species, ' ',"
         +m_schema+".surveys.transition)"
      +" WHERE obs_publisher_did LIKE '%" + pubdid + "'"); // FIXME replace LIKE with exact match but concat ivoprefix+pubdid

   cut_db_params_t cut_params;

   LOG_STREAM << qstr << endl;

   try
   {
      pqxx::result r{txn.exec(qstr)};

      // Results can be accessed and iterated again.
      // Even after the connection has been closed.

      string aa(10*80,'a');
      long int row_count = r.size();// FIXME check type
      LOG_STREAM << "RowsCount: " << row_count << endl;

      if(row_count != 1)
         throw invalid_argument("found " + to_string(row_count) +  " rows instead of one for pubdid: " + pubdid);

      for (auto row: r)
      {
         cut_params.header        = aa;//row["header"].as<std::string>();
         cut_params.filename      = row["access_url"].as<std::string>();
         cut_params.hdunum        = 1;//row["hdunum"].as<unsigned int>();
         cut_params.storage_path  = row["storage_path"].as<std::string>();

         const double default_when_null = 0.0;
         cut_params.rest_frequency = row["rest_frequency"].as<double>(default_when_null);
         const string empty_str;
         cut_params.velocity_fits_unit = row["velocity_fits_unit"].as<std::string>(empty_str);

         cut_params.name = row["name"].as<std::string>(empty_str);
         cut_params.species = row["species"].as<std::string>(empty_str);
         cut_params.transition = row["transition"].as<std::string>(empty_str);

         // Iterate over fields in a row.
         //for (auto field: row) LOG_STREAM << field.c_str() << " "; LOG_STREAM << endl;
      }
   }
   catch (pqxx::sql_error const &e)
   {
      throw runtime_error("SQL error: " + string{e.what()} + " Query: " + string{e.query()});
   }

   return cut_params;
};



