//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "fMain.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "AdvGrid"
#pragma link "AdvObj"
#pragma link "AdvUtil"
#pragma link "BaseGrid"
#pragma link "DBAdvGrid"
#pragma link "AdvSplitter"
#pragma link "AdvEdit"
#pragma link "vcl.wwdbcomb"
#pragma link "vcl.wwdbedit"
#pragma link "vcl.wwdotdot"
#pragma link "AdvOfficePager"
#pragma link "MemDS"
#pragma link "VirtualTable"
#pragma link "DBAccess"
#pragma link "Uni"
#pragma link "vcl.wwdbdatetimepicker"
#pragma link "PostgreSQLUniProvider"
#pragma link "UniProvider"
#pragma resource "*.dfm"

TfrmMain *frmMain;
//---------------------------------------------------------------------------
__fastcall TfrmMain::TfrmMain(TComponent* Owner)
  : TForm(Owner)
{
  pagesLoaded = 0;

  accountSid = "AC717f06b3c10bbecff42487426e34fdb3";
  authHeader = "Basic QUM3MTdmMDZiM2MxMGJiZWNmZjQyNDg3NDI2ZTM0ZmRiMzphMGRlYzEyOGMxYjVhMzIzMTkwOWJhODFkYTg5NDdiNg==";

  TDateTime dtNow = Date();
  unsigned short y, m, d;
  dtNow.DecodeDate(&y, &m, &d);

  for(int i = 0; i < 12; i++)
  dbcbYear->Items->Add(y - i);
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnExitClick(TObject *Sender)
{
  Close();
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnMessagesClick(TObject *Sender)
{
  bool saveToDB = cbSaveToDB->Checked;
  String nextPageUri = BuildMessageRequestURL();

  // Reset grid, pagination, and total message count
  grdData->ClearAll();
  grdData->RowCount = 1;
  pagesLoaded = 0;
  int totalMessages = 0;  // Reset total message count

  if (saveToDB)
    Memo2->Lines->Add("Fetching messages to be saved in the database...");
  else
    Memo2->Lines->Add("Fetching messages from Twilio for AdvGrid...");

  do {
    TRESTClient* pRESTClient = new TRESTClient(NULL);
    pRESTClient->BaseURL = "https://api.twilio.com";

    TRESTRequest* pRESTRequest = new TRESTRequest(NULL);
    pRESTRequest->Client = pRESTClient;

    TRESTResponse* pRESTResponse = new TRESTResponse(NULL);
    pRESTRequest->Response = pRESTResponse;

    pRESTRequest->Method = rmGET;
    pRESTRequest->Resource = nextPageUri;

    TRESTRequestParameter* param = pRESTRequest->Params->AddItem();
    param->Name = "Authorization";
    param->Kind = pkHTTPHEADER;
    param->Options = TRESTRequestParameterOptions() << poDoNotEncode;
    param->Value = authHeader;

    Memo1->Lines->Add("Fetching Messages from URI: " + pRESTRequest->GetFullRequestURL());
    pRESTRequest->Execute();

    TJSONObject* jObj = dynamic_cast<TJSONObject*>(pRESTResponse->JSONValue);
    if (!jObj) {
      Memo1->Lines->Add("No JSON object returned.");
      break;
    }

    TJSONArray* jaMessages = dynamic_cast<TJSONArray*>(jObj->GetValue("messages"));
    if (jaMessages && jaMessages->Count > 0) {
      if (pagesLoaded == 0) {
        GetFieldsFromJsonArray(jaMessages);
        LoadJsonArray(jaMessages, "");
      } else {
        AppendJsonArray(jaMessages);
      }

      if (saveToDB)
        SaveMessagesToDB(jaMessages);

      pagesLoaded++;
      totalMessages += jaMessages->Count;

      Memo2->Lines->Add("Loaded page #" + IntToStr(pagesLoaded));
      Memo2->Lines->Add("Messages on this page: " + IntToStr(jaMessages->Count));

      if (saveToDB)
        Memo2->Lines->Add("Total messages saved to database: " + IntToStr(totalMessages));
      else
        Memo2->Lines->Add("Total messages loaded: " + IntToStr(totalMessages));

    } else {
      Memo2->Lines->Add("No messages found on this page.");
      break;
    }

    if (jObj->Get("next_page_uri"))
      nextPageUri = jObj->Get("next_page_uri")->JsonValue->Value();
    else
      nextPageUri = "";  // No more pages

    delete pRESTClient;
    delete pRESTRequest;
    delete pRESTResponse;

  } while (!nextPageUri.IsEmpty() && nextPageUri != "null");

  Memo2->Lines->Add("Get messages complete.");
}

//---------------------------------------------------------------------------
void TfrmMain::SaveMessagesToDB(TJSONArray* jaMessages)
{
  if (!jaMessages || jaMessages->Count == 0)
    return;

  if (!ucTwilioDB->Connected)
    ucTwilioDB->Connected = true;

  if (!uqMessages->Active)
    uqMessages->Active = true;

  for (int i = 0; i < jaMessages->Count; i++)
  {
    TJSONObject* jso = dynamic_cast<TJSONObject*>(jaMessages->Items[i]);
    if (!jso)
      continue;

    String sid = jso->GetValue("sid")->Value();

    // Check if the message already exists
  uqMessages->Close();
  uqMessages->SQL->Text = "SELECT * FROM public.messages WHERE sid = :sid";
  uqMessages->ParamByName("sid")->AsString = sid;
  uqMessages->Open();

  if (uqMessages->IsEmpty())
  {
    uqMessages->Append();
  }
  else
  {
    uqMessages->Edit();
  }
  uqMessages->FieldByName("sid")->AsString = sid;
  uqMessages->FieldByName("account_sid")->AsString = jso->GetValue("account_sid")->Value();
  uqMessages->FieldByName("api_version")->AsString = jso->GetValue("api_version")->Value();
  uqMessages->FieldByName("body")->AsString = jso->GetValue("body")->Value();
  uqMessages->FieldByName("date_created")->AsDateTime = ParseTwilioDateTime(jso->GetValue("date_created")->Value());
  uqMessages->FieldByName("date_sent")->AsDateTime = ParseTwilioDateTime(jso->GetValue("date_sent")->Value());
  uqMessages->FieldByName("date_updated")->AsDateTime = ParseTwilioDateTime(jso->GetValue("date_updated")->Value());
  uqMessages->FieldByName("direction")->AsString = jso->GetValue("direction")->Value();
  uqMessages->FieldByName("error_code")->AsInteger = jso->GetValue("error_code")->Value().ToIntDef(0);
  uqMessages->FieldByName("error_message")->AsString = jso->GetValue("error_message")->Value();
  uqMessages->FieldByName("from_number")->AsString = jso->GetValue("from")->Value();
  uqMessages->FieldByName("messaging_service_sid")->AsString = jso->GetValue("messaging_service_sid")->Value();
  uqMessages->FieldByName("num_media")->AsInteger = jso->GetValue("num_media")->Value().ToIntDef(0);
  uqMessages->FieldByName("num_segments")->AsInteger = jso->GetValue("num_segments")->Value().ToIntDef(0);
  uqMessages->FieldByName("price")->AsFloat = jso->GetValue("price")->Value().ToDouble();
  uqMessages->FieldByName("price_unit")->AsString = jso->GetValue("price_unit")->Value();
  uqMessages->FieldByName("status")->AsString = jso->GetValue("status")->Value();
  uqMessages->FieldByName("to_number")->AsString = jso->GetValue("to")->Value();
  uqMessages->FieldByName("uri")->AsString = jso->GetValue("uri")->Value();
  uqMessages->Post();
  }
}
//---------------------------------------------------------------------------
TDateTime TfrmMain::ParseTwilioDateTime(String dateStr)
{
  // Twilio format: "Wed, 12 Feb 2025 18:21:17 +0000"
  std::map<UnicodeString, int> monthMap;
  monthMap["Jan"] = 1; monthMap["Feb"] = 2; monthMap["Mar"] = 3;
	monthMap["Apr"] = 4; monthMap["May"] = 5; monthMap["Jun"] = 6;
	monthMap["Jul"] = 7; monthMap["Aug"] = 8; monthMap["Sep"] = 9;
	monthMap["Oct"] = 10; monthMap["Nov"] = 11; monthMap["Dec"] = 12;

	String cleanedDateStr = dateStr.SubString(6, 20);

    TStringList *parts = new TStringList();
    parts->Delimiter = ' ';
    parts->StrictDelimiter = true;
    parts->DelimitedText = cleanedDateStr;

    if (parts->Count < 4) {
        delete parts;
        throw EConvertError("Invalid Twilio date format: " + dateStr);
    }

    int day = parts->Strings[0].ToInt();
    UnicodeString monthStr = parts->Strings[1];
    int year = parts->Strings[2].ToInt();
    UnicodeString timeStr = parts->Strings[3];

    if (monthMap.find(monthStr) == monthMap.end()) {
        delete parts;
        throw EConvertError("Invalid month name: " + monthStr);
    }
    int month = monthMap[monthStr];

    UnicodeString formattedDate = UnicodeString::Format(
        L"%04d-%02d-%02d %s", ARRAYOFCONST((year, month, day, timeStr))
    );

	TFormatSettings formatSettings;
	GetLocaleFormatSettings(0, formatSettings);
    formatSettings.ShortDateFormat = "yyyy-mm-dd";
    formatSettings.LongTimeFormat = "hh:nn:ss";
    formatSettings.DateSeparator = '-';
    formatSettings.TimeSeparator = ':';

    TDateTime result = StrToDateTime(formattedDate, formatSettings);

    delete parts;
    return result;
}
//---------------------------------------------------------------------------
String TfrmMain::BuildMessageRequestURL()
{
  String url = "/2010-04-01/Accounts/" + accountSid + "/Messages.json";
  String pageSizeStr = edtPageSize->Text.Trim();

  bool hasStartDate = !dtpStartDate->Text.IsEmpty();
  bool hasEndDate = !dtpEndDate->Text.IsEmpty();

  if (hasStartDate || hasEndDate)
  {
    TDateTime now = Now();
    TDateTime d1, d2;

    if (hasStartDate) d1 = dtpStartDate->Date;
    if (hasEndDate) d2 = dtpEndDate->Date;

    if (hasStartDate && hasEndDate && d1 > d2)
    {
      throw Exception("Invalid date range: The start date cannot be more recent than the end date.");
    }

    if (hasStartDate && hasEndDate)
    {
      if (d1 == d2)
      {
        url += "?DateSent=" + FormatDateTime("yyyy-mm-dd", d1);
      }
      else
      {
        url += "?DateSent>=" + FormatDateTime("yyyy-mm-dd", d1) +
               "&DateSent<=" + FormatDateTime("yyyy-mm-dd", d2);
      }
    }
    else if (hasStartDate)
    {
      url += "?DateSent=" + FormatDateTime("yyyy-mm-dd", d1);
    }
    else if (hasEndDate)
    {
      TDateTime oldestAvailable = now - 395;  // Twilio keeps messages for exactly 395 days
      url += "?DateSent>=" + FormatDateTime("yyyy-mm-dd", oldestAvailable) +
             "&DateSent<=" + FormatDateTime("yyyy-mm-dd", d2);
    }

    if (!pageSizeStr.IsEmpty())
      url += "&PageSize=" + pageSizeStr;
  }
  else
  {
    if (!pageSizeStr.IsEmpty())
      url += "?PageSize=" + pageSizeStr;
  }

  return url;
}

//---------------------------------------------------------------------------
void TfrmMain::GetFieldsFromJsonArray(TJSONArray* jaData)
{
  String fieldname;
  String str;
  TJSONObject* jobj;
  TJSONObject* jso;
  TJSONArray* ja;
  int row;

  fieldsList = new TStringList;
  jso = (TJSONObject*)jaData->Items[0];
  for( int i = 0; i < jso->Count; i++ ){
    fieldname = jso->Pairs[i]->JsonString->Value();
    str = fieldname + "=0";
    fieldsList->Add( str );
  }
}
//---------------------------------------------------------------------------
void TfrmMain::LoadJsonArray(TJSONArray* jaData, String detail)
{
  TJSONObject* jobj;
  TJSONObject* jso;
  TJSONArray* ja;
  int row;

  grdData->ClearAll();
  grdData->RowCount = 1;
  grdData->StartUpdate();
  jso = (TJSONObject*)jaData->Items[0];
  grdData->ColCount = jso->Count;
  for( int e = 0; e < jso->Count; e++ )
    grdData->Cells[e+1][0] = jso->Pairs[e]->JsonString->Value();

  for( int i = 0; i < jaData->Count; i++ ){
    jso = (TJSONObject*)jaData->Items[i];
    grdData->RowCount++;
    row = grdData->RowCount - 1;
    for( int e = 0; e < jso->Count; e++ )
      grdData->Cells[e+1][row] = jso->Pairs[e]->JsonValue->Value();
  }
  grdData->EndUpdate();
}
//---------------------------------------------------------------------------
void TfrmMain::AppendJsonArray(TJSONArray* jaData)
{
  if(!jaData || jaData->Count == 0)
    return;

  grdData->BeginUpdate();
  for(int i = 0; i < jaData->Count; i++)
  {
  TJSONObject* jso = dynamic_cast<TJSONObject*>(jaData->Items[i]);
    if(!jso)
      continue;

    grdData->RowCount++;
    int row = grdData->RowCount - 1;
    for(int e = 0; e < jso->Count; e++)
    {
      grdData->Cells[e+1][row] = jso->Pairs[e]->JsonValue->Value();
  }
  }
  grdData->EndUpdate();
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::dbcbMonthCloseUp(TwwDBComboBox *Sender, bool Select)
{
  SetDatePickersFromMonthYear();
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::dbcbYearCloseUp(TwwDBComboBox *Sender, bool Select)
{
  SetDatePickersFromMonthYear();
}
//---------------------------------------------------------------------------
void TfrmMain::SetDatePickersFromMonthYear()
{
  int year = 0;
  int month = 0;
  TDateTime startDate;
  TDateTime endDate;

  if( !dbcbYear->Value.IsEmpty() )
    year = StrToInt( dbcbYear->Value );

  if( !dbcbMonth->Value.IsEmpty() )
    month = StrToInt( dbcbMonth->Value );

  if( month > 0 && year > 0 ){
    startDate = StartOfAMonth( year, month );
    endDate = EndOfAMonth( year, month );
    dtpStartDate->Date = startDate;
    dtpEndDate->Date = endDate;
  }
  else if (year > 0){
    startDate = StartOfAMonth( year, 1 );
    endDate = EndOfAMonth( year, 12 );
    dtpStartDate->Date = startDate;
    dtpEndDate->Date = endDate;
  }
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::Button1Click(TObject *Sender)
{
  TDateTime dt;

  dt = ParseTwilioDateTime( "Wed, 12 Feb 2025 18:21:17 +0000" );
  Memo1->Lines->Add( dt.DateTimeString() );
}
//---------------------------------------------------------------------------
void __fastcall TfrmMain::btnDBMessagesClick(TObject *Sender)
{
  Memo1->Lines->Add("Fetching messages from the database...");

  if (!ucTwilioDB->Connected)
    ucTwilioDB->Connected = true;

  bool hasStartDate = !dtpStartDate->Text.IsEmpty();
  bool hasEndDate = !dtpEndDate->Text.IsEmpty();
  TDateTime startDate, endDate;
  TDateTime now = Now();
  TDateTime oldestAvailable = now - 395;  // EXACTLY 395 days for Twilio's retention period

  if (hasStartDate) startDate = dtpStartDate->Date;
  if (hasEndDate) endDate = dtpEndDate->Date;

  // **Validation: Ensure Start Date is not more recent than End Date**
  if (hasStartDate && hasEndDate && startDate > endDate)
  {
    throw Exception("Invalid date range: The start date cannot be more recent than the end date.");
  }

  uqDBMessages->Close();

  if (hasStartDate && hasEndDate)
  {
    if (startDate == endDate)  // **Fix: Handle same start & end date**
    {
      uqDBMessages->SQL->Text =
        "SELECT * FROM public.messages "
        "WHERE date_sent::DATE = :selectedDate "
        "ORDER BY date_sent ASC";
      uqDBMessages->ParamByName("selectedDate")->AsDateTime = startDate;
    }
    else
    {
      uqDBMessages->SQL->Text =
        "SELECT * FROM public.messages "
        "WHERE date_sent::DATE >= :startDate AND date_sent::DATE <= :endDate "
        "ORDER BY date_sent ASC";
      uqDBMessages->ParamByName("startDate")->AsDateTime = startDate;
      uqDBMessages->ParamByName("endDate")->AsDateTime = endDate;
    }
  }
  else if (hasStartDate)
  {
    uqDBMessages->SQL->Text =
      "SELECT * FROM public.messages "
      "WHERE date_sent::DATE = :startDate "
      "ORDER BY date_sent ASC";
    uqDBMessages->ParamByName("startDate")->AsDateTime = startDate;
  }
  else if (hasEndDate)
  {
    uqDBMessages->SQL->Text =
      "SELECT * FROM public.messages "
      "WHERE date_sent >= :oldestAvailable AND date_sent::DATE <= :endDate "
      "ORDER BY date_sent ASC";
    uqDBMessages->ParamByName("oldestAvailable")->AsDateTime = oldestAvailable;
    uqDBMessages->ParamByName("endDate")->AsDateTime = endDate;
  }
  else
  {
    uqDBMessages->SQL->Text =
      "SELECT * FROM public.messages "
      "ORDER BY date_sent ASC";
  }

  Memo1->Lines->Add("Executing query: " + uqDBMessages->SQL->Text);
  uqDBMessages->Open();

  if (uqDBMessages->IsEmpty()) {
    Memo2->Lines->Add("No messages found for the selected date range.");
    return;
  }

  Memo2->Lines->Add("Messages retrieved: " + IntToStr(uqDBMessages->RecordCount));

  dsMessages->DataSet = uqDBMessages;
  dbgrdDatabase->Refresh();

  Memo1->Lines->Add("Database messages loaded into grid.");
}
//---------------------------------------------------------------------------

