//+------------------------------------------------------------------+
//|                                                    HttpPoster.mqh|
//+------------------------------------------------------------------+
#ifndef HTTPPOSTER_MQH
#define HTTPPOSTER_MQH

#include "WinINet.mqh"
//+------------------------------------------------------------------+
//| Wraps the three-handle WinINet pipeline (session → connection →  |
//| request) behind a single PostJson() method. The session handle   |
//| is opened once in Init() and reused for every subsequent POST,   |
//| which avoids the overhead of re-negotiating a new session on     |
//| every trade event.                                               |
//+------------------------------------------------------------------+
class CHttpPoster
  {
private:
   string            m_host;        // hostname or IP, e.g. "127.0.0.1"
   ushort            m_port;        // TCP port, e.g. 8787
   string            m_path;        // resource path, e.g. "/trade_event"
   long              m_hsession;    // WinINet session handle, opened in Init()
   int               m_last_status; // HTTP status code from the last call
   bool              m_silent;      // when true, suppresses internal error prints

   bool              OpenHandles(long &hconn, long &hreq) const;

public:
                     CHttpPoster(void);
                    ~CHttpPoster(void);

   bool              Init(const string &host, ushort port, const string &path,
                          const string &user_agent);
   bool              PostJson(const string &json_body);
   int               LastStatus(void) const;
   void              SetSilent(bool silent);
   void              Deinit(void);
  };
//+------------------------------------------------------------------+
//| Constructor — sets all fields to safe initial values.            |
//+------------------------------------------------------------------+
CHttpPoster::CHttpPoster(void)
  {
   m_host        = "";
   m_port        = 8787;
   m_path        = "/trade_event";
   m_hsession    = 0;
   m_last_status = 0;
   m_silent      = false;
  }
//+------------------------------------------------------------------+
//| Destructor — closes the session handle if still open.            |
//+------------------------------------------------------------------+
CHttpPoster::~CHttpPoster(void)
  {
   Deinit();
  }
//+------------------------------------------------------------------+
//| Opens the WinINet session handle and stores configuration.       |
//| The session is kept open for the lifetime of the EA so that      |
//| subsequent PostJson() calls can reuse it without reopening.      |
//+------------------------------------------------------------------+
bool CHttpPoster::Init(const string &host, ushort port, const string &path,
                       const string &user_agent)
  {
   m_host = host;
   m_port = port;
   m_path = path;

//--- INTERNET_OPEN_TYPE_DIRECT = 1; bypasses any proxy for local connections
   m_hsession = ::InternetOpenW(user_agent, 1, NULL, NULL, 0);
   if(m_hsession == 0)
     {
      ::PrintFormat("CHttpPoster::Init: InternetOpenW failed (GetLastError=%d)",
                    ::GetLastError());
      return(false);
     }
   return(true);
  }
//+------------------------------------------------------------------+
//| Enables or disables suppression of internal diagnostic prints.   |
//| When silent is true, PostJson() and OpenHandles() do not write   |
//| to the Experts tab on failure; the caller handles all logging.   |
//+------------------------------------------------------------------+
void CHttpPoster::SetSilent(bool silent)
  {
   m_silent = silent;
  }
//+------------------------------------------------------------------+
//| Opens a fresh connection handle and request handle for one POST. |
//| Returns false if either handle cannot be obtained.               |
//+------------------------------------------------------------------+
bool CHttpPoster::OpenHandles(long &hconn, long &hreq) const
  {
//--- INTERNET_SERVICE_HTTP = 3; credentials are NULL (no auth)
   hconn = ::InternetConnectW(m_hsession, m_host, m_port, NULL, NULL, 3, 0, 0);
   if(hconn == 0)
     {
      if(!m_silent)
         ::PrintFormat("CHttpPoster::OpenHandles: InternetConnectW failed (GetLastError=%d)",
                       ::GetLastError());
      return(false);
     }

//--- INTERNET_FLAG_RELOAD = 0x80000000: bypasses the WinINet cache so
//--- every POST actually reaches the server rather than being served
//--- from a cached response
   hreq = ::HttpOpenRequestW(hconn, "POST", m_path, NULL, NULL, NULL,
                             0x80000000, 0);
   if(hreq == 0)
     {
      if(!m_silent)
         ::PrintFormat("CHttpPoster::OpenHandles: HttpOpenRequestW failed (GetLastError=%d)",
                       ::GetLastError());
      ::InternetCloseHandle(hconn);
      hconn = 0;
      return(false);
     }
   return(true);
  }
//+-------------------------------------------------------------------+
//| Posts json_body to the configured endpoint as an HTTP POST with   |
//| Content-Type: application/json. Returns true if the server        |
//| replies with HTTP 200. The connection and request handles are     |
//| opened fresh and closed within this call; only the session handle |
//| persists across calls.                                            |
//+-------------------------------------------------------------------+
bool CHttpPoster::PostJson(const string &json_body)
  {
   if(m_hsession == 0)
     {
      if(!m_silent)
         ::Print("CHttpPoster::PostJson: not initialized — call Init() first");
      return(false);
     }

   long hconn = 0;
   long hreq  = 0;
   if(!OpenHandles(hconn, hreq))
      return(false);

//--- convert the JSON string to a UTF-8 byte array; StringToCharArray
//--- writes a null terminator at the end, which must be excluded from
//--- the byte count passed to HttpSendRequestW
   uchar body_bytes[];
   int body_len = ::StringToCharArray(json_body, body_bytes, 0, -1, CP_UTF8) - 1;
   if(body_len < 0)
      body_len = 0;

//--- build the header block: Content-Type and Content-Length in one
//--- CRLF-terminated string; HttpSendRequestW appends these to the
//--- standard HTTP/1.1 headers it generates internally
   string headers = "Content-Type: application/json\r\n"
                    + "Content-Length: " + ::IntegerToString(body_len) + "\r\n";

   bool sent = ::HttpSendRequestW(hreq, headers, ::StringLen(headers),
                                  body_bytes, (uint)body_len);

   m_last_status = 0;
   if(sent)
     {
      //--- HTTP_QUERY_STATUS_CODE = 19; combined with HTTP_QUERY_FLAG_NUMBER
      //--- (0x20000000) it writes the numeric status code into a DWORD buffer
      uint status = 0;
      uint status_len = 4;
      uint index = 0;
      if(::HttpQueryInfoW(hreq, 19 | 0x20000000, status, status_len, index))
         m_last_status = (int)status;
     }
   else
     {
      if(!m_silent)
         ::PrintFormat("CHttpPoster::PostJson: HttpSendRequestW failed (GetLastError=%d)",
                       ::GetLastError());
     }

//--- close per-request handles regardless of success or failure
   ::InternetCloseHandle(hreq);
   ::InternetCloseHandle(hconn);

   return(sent && m_last_status == 200);
  }
//+------------------------------------------------------------------+
//| Returns the HTTP status code from the most recent PostJson call. |
//+------------------------------------------------------------------+
int CHttpPoster::LastStatus(void) const
  {
   return(m_last_status);
  }
//+------------------------------------------------------------------+
//| Closes the session handle. Called by the destructor and by the   |
//| EA's OnDeinit() to release WinINet resources cleanly.            |
//+------------------------------------------------------------------+
void CHttpPoster::Deinit(void)
  {
   if(m_hsession != 0)
     {
      ::InternetCloseHandle(m_hsession);
      m_hsession = 0;
     }
  }

#endif // HTTPPOSTER_MQH
//+------------------------------------------------------------------+