//+------------------------------------------------------------------+
//|                                        TestSessionAnalytics.mq5  |
//+------------------------------------------------------------------+

#include <SessionDashboard/SessionEnums.mqh>
#include <SessionDashboard/SessionClassifier.mqh>
#include <SessionDashboard/SessionAggregator.mqh>

#define ASSERT(condition, message) TestAssert((condition), (message))

int g_pass_count = 0;
int g_fail_count = 0;

//+------------------------------------------------------------------+
//| TestAssert                                                       |
//| Prints a pass or fail message for a single test condition and    |
//| tracks the running pass and fail counts.                         |
//+------------------------------------------------------------------+
void TestAssert(bool condition, string message)
  {
   if(condition)
     {
      g_pass_count++;
      ::Print("PASS: ", message);
     }
   else
     {
      g_fail_count++;
      ::Print("FAIL: ", message);
     }
  }

//+------------------------------------------------------------------+
//| BuildDateTime                                                    |
//| Builds a UTC datetime from a fixed calendar date and the given   |
//| hour, for use in boundary condition tests.                       |
//+------------------------------------------------------------------+
datetime BuildDateTime(int hour)
  {
   MqlDateTime dt;
   dt.year = 2026;
   dt.mon  = 1;
   dt.day  = 15;
   dt.hour = hour;
   dt.min  = 30;
   dt.sec  = 0;
   return(::StructToTime(dt));
  }

//+------------------------------------------------------------------+
//| OnStart                                                          |
//| Runs classification, aggregation, and hold time tests and        |
//| prints a final summary of pass and fail counts.                  |
//+------------------------------------------------------------------+
void OnStart(void)
  {
   CSessionClassifier classifier;
   classifier.LoadDefaults();
//--- test 1: 14:30 UTC falls in the London/New York overlap, expect London
   ENUM_TRADING_SESSION overlap_result = classifier.Classify(BuildDateTime(14));
   ASSERT(overlap_result == SESSION_LONDON,
          "14:30 UTC overlap classifies as SESSION_LONDON");
//--- test 2: 00:30 UTC falls inside the midnight-crossing Sydney boundary
   ENUM_TRADING_SESSION sydney_result = classifier.Classify(BuildDateTime(0));
   ASSERT(sydney_result == SESSION_SYDNEY,
          "00:30 UTC classifies as SESSION_SYDNEY across midnight");
//--- test 3: 03:00 UTC also falls inside the Sydney boundary
   ENUM_TRADING_SESSION sydney_result_2 = classifier.Classify(BuildDateTime(3));
   ASSERT(sydney_result_2 == SESSION_SYDNEY,
          "03:00 UTC classifies as SESSION_SYDNEY across midnight");
//--- test 4: 10:00 UTC falls inside the London-only window, before overlap
   ENUM_TRADING_SESSION london_only = classifier.Classify(BuildDateTime(10));
   ASSERT(london_only == SESSION_LONDON,
          "10:00 UTC classifies as SESSION_LONDON before the overlap");
//--- build a set of deal records to exercise the aggregator
   CDealRecord records[6];
   for(int i = 0; i < 6; i++)
      records[i].session_type = SESSION_LONDON;
   records[0].profit      = 100.0;
   records[0].open_time   = BuildDateTime(8);
   records[0].close_time  = BuildDateTime(8) + 1800;
   records[1].profit      = 50.0;
   records[1].open_time   = BuildDateTime(8);
   records[1].close_time  = BuildDateTime(8) + 3600;
   records[2].profit      = 75.0;
   records[2].open_time   = BuildDateTime(8);
   records[2].close_time  = BuildDateTime(8) + 5400;
   records[3].profit      = -20.0;
   records[3].open_time   = BuildDateTime(8);
   records[3].close_time  = BuildDateTime(8);
   records[4].profit      = -30.0;
   records[4].open_time   = BuildDateTime(8);
   records[4].close_time  = BuildDateTime(8);
   records[5].profit      = 25.0;
   records[5].open_time   = BuildDateTime(8);
   records[5].close_time  = BuildDateTime(8);
//--- test 5: win rate for 4 wins out of 6 deals should be 66.7%
   CSessionAggregator aggregator;
   CSessionMetrics    metrics[];
   int                metrics_count = aggregator.Aggregate(records, 6, metrics);
   double win_rate = ((double)metrics[SESSION_LONDON].win_count /
                      (double)metrics[SESSION_LONDON].trade_count) * 100.0;
   ASSERT(::MathAbs(win_rate - 66.7) < 0.05,
          "win rate for 4 of 6 winning deals rounds to 66.7%");
//--- test 6: net P&L for the same six deals should sum to 200.0
   ASSERT(::MathAbs(metrics[SESSION_LONDON].net_pnl - 200.0) < 0.001,
          "net P&L across the six test deals sums to 200.0");
//--- test 7: average hold time for three deals of 1800, 3600, 5400 seconds
   CDealRecord hold_records[3];
   hold_records[0].session_type = SESSION_TOKYO;
   hold_records[0].profit       = 10.0;
   hold_records[0].open_time    = BuildDateTime(1);
   hold_records[0].close_time   = BuildDateTime(1) + 1800;
   hold_records[1].session_type = SESSION_TOKYO;
   hold_records[1].profit       = 10.0;
   hold_records[1].open_time    = BuildDateTime(1);
   hold_records[1].close_time   = BuildDateTime(1) + 3600;
   hold_records[2].session_type = SESSION_TOKYO;
   hold_records[2].profit       = 10.0;
   hold_records[2].open_time    = BuildDateTime(1);
   hold_records[2].close_time   = BuildDateTime(1) + 5400;
   CSessionMetrics hold_metrics[];
   aggregator.Aggregate(hold_records, 3, hold_metrics);
   ASSERT(::MathAbs(hold_metrics[SESSION_TOKYO].average_hold_time - 3600.0) < 0.001,
          "average hold time for 1800, 3600, 5400 seconds computes to 3600.0");
//--- print the final summary of pass and fail counts
   ::Print("TestSessionAnalytics: ", g_pass_count, " passed, ", g_fail_count, " failed");
  }
//+------------------------------------------------------------------+