unit Api.ServiceImpl;

interface

uses
  XData.Server.Module, XData.Service.Common, Api.Database, Data.DB,
  System.SysUtils, System.Generics.Collections, XData.Sys.Exceptions, System.StrUtils,
  System.Hash, System.Classes, Common.Logging, System.JSON, Api.Service, VCL.Forms;


type
  [ServiceImplementation]
  TApiService = class(TInterfacedObject, IApiService)
  strict private
  ApiDB: TApiDatabaseModule;
  private
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
  public
    function GetBadgeCounts: TJSONObject;
    function GetComplaintList: TJSONObject;
    function GetUnitList: TJSONObject;
    function GetComplaintMap: TJSONObject;
    function GetUnitMap: TJSONObject;
    function GetComplaintDetails(const ComplaintId: string): TJSONObject;
  end;

implementation

uses
  uLibrary;

procedure TApiService.AfterConstruction;
begin
  inherited;
  ApiDB := TApiDatabaseModule.Create(nil);
  Logger.Log(3, 'ApiDatabaseModule created');
end;

procedure TApiService.BeforeDestruction;
begin
  ApiDB.Free;
  inherited;
  Logger.Log(3, 'ApiDatabaseModule destroyed');
end;


function TApiService.GetBadgeCounts: TJSONObject;
begin
  Logger.Log(3, '---TApiService.GetBadgeCounts initiated');

  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  try
    with ApiDB.uqBadgeCounts do
    begin
      Open;
      try
        Result.AddPair('BadgeComplaints', TJSONNumber.Create(FieldByName('COMPLAINTS').AsInteger));
        Result.AddPair('BadgeUnits', TJSONNumber.Create(FieldByName('UNITS').AsInteger));
      finally
        Close;
      end;
    end;
  except
    on E: Exception do
    begin
      Logger.Log(3, '---TApiService.GetBadgeCounts End (error): ' + E.Message);
      raise EXDataHttpException.Create(500, 'Failed to load badge counts');
    end;
  end;

  Logger.Log(3, '---TApiService.GetBadgeCounts End');
end;

function TApiService.GetComplaintMap: TJSONObject;
var
  data: TJSONArray;
  emitted: Integer;
  item: TJSONObject;
  UnitsByComplaintMap: TObjectDictionary<string,TJSONArray>;
  complaintId: string;
  unitArray: TJSONArray;
  unitStatus: string;
  latestUpdate: TDateTime;
  unitObj: TJSONObject;
begin
  Logger.Log(3,'---TApiService.GetComplaintMap initiated');

  Result:=TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  data:=TJSONArray.Create;
  UnitsByComplaintMap:=TObjectDictionary<string,TJSONArray>.Create([doOwnsValues]);

  try
    ApiDB.uqMapComplaintUnitsList.Open;
    while not ApiDB.uqMapComplaintUnitsList.Eof do
    begin
      complaintId:=ApiDB.uqMapComplaintUnitsListCOMPLAINTID.AsString;
      if not UnitsByComplaintMap.TryGetValue(complaintId,unitArray) then
      begin
        unitArray:=TJSONArray.Create;
        UnitsByComplaintMap.Add(complaintId,unitArray);
      end;

      unitStatus:='Dispatched';
      if not ApiDB.uqMapComplaintUnitsListDATECLEARED.IsNull then
        unitStatus:='Cleared'
      else if not ApiDB.uqMapComplaintUnitsListDATEARRIVED.IsNull then
        unitStatus:='On Scene'
      else if not ApiDB.uqMapComplaintUnitsListDATERESPONDED.IsNull then
        unitStatus:='Enroute';

      latestUpdate:=0;
      if not ApiDB.uqMapComplaintUnitsListDATEDISPATCHED.IsNull then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATEDISPATCHED.AsDateTime;
      if (not ApiDB.uqMapComplaintUnitsListDATERESPONDED.IsNull) and
         (ApiDB.uqMapComplaintUnitsListDATERESPONDED.AsDateTime>latestUpdate) then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATERESPONDED.AsDateTime;
      if (not ApiDB.uqMapComplaintUnitsListDATEARRIVED.IsNull) and
         (ApiDB.uqMapComplaintUnitsListDATEARRIVED.AsDateTime>latestUpdate) then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATEARRIVED.AsDateTime;
      if (not ApiDB.uqMapComplaintUnitsListDATECLEARED.IsNull) and
         (ApiDB.uqMapComplaintUnitsListDATECLEARED.AsDateTime>latestUpdate) then
        latestUpdate:=ApiDB.uqMapComplaintUnitsListDATECLEARED.AsDateTime;

      unitObj:=TJSONObject.Create;
      unitObj.AddPair('Unit',ApiDB.uqMapComplaintUnitsListUNITNAME.AsString);
      unitObj.AddPair('Status',unitStatus);
      if latestUpdate<>0 then
        unitObj.AddPair('Updated',FormatDateTime('yyyy-mm-dd hh:nn:ss',latestUpdate))
      else
        unitObj.AddPair('Updated','');

      unitArray.AddElement(unitObj);
      ApiDB.uqMapComplaintUnitsList.Next;
    end;

    try
      emitted:=0;
      ApiDB.uqMapComplaints.Open;
      while not ApiDB.uqMapComplaints.Eof do
      begin
        item:=TJSONObject.Create;

        item.AddPair('ComplaintId',ApiDB.uqMapComplaintsCOMPLAINTID.AsString);
        item.AddPair('DispatchDistrict',ApiDB.uqMapComplaintsDISPATCHDISTRICT.AsString);
        item.AddPair('DispatchCodeDesc',ApiDB.uqMapComplaintsDISPATCH_CODE_DESC.AsString);
        item.AddPair('DispatchCodeCategory',ApiDB.uqMapComplaintsDISPATCHCODECATEGORY.AsString);
        item.AddPair('Priority',ApiDB.uqMapComplaintsPRIORITY.AsString);
        item.AddPair('priorityKey',ApiDB.uqMapComplaintspriorityKey.AsString);
        item.AddPair('pngName',ApiDB.uqMapComplaintspngName.AsString);
        item.AddPair('Address',ApiDB.uqMapComplaintsADDRESS.AsString);

        complaintId:=ApiDB.uqMapComplaintsCOMPLAINTID.AsString;
        if UnitsByComplaintMap.TryGetValue(complaintId,unitArray) then
          item.AddPair('Units',TJSONArray(unitArray.Clone))
        else
          item.AddPair('Units',TJSONArray.Create);

        item.AddPair('Lat',TJSONNumber.Create(ApiDB.uqMapComplaintsLAT.AsFloat));
        item.AddPair('Lng',TJSONNumber.Create(ApiDB.uqMapComplaintsLNG.AsFloat));

        data.AddElement(item);
        Inc(emitted);
        ApiDB.uqMapComplaints.Next;
      end;

      Result.AddPair('count',TJSONNumber.Create(data.Count));
      Result.AddPair('returned',TJSONNumber.Create(emitted));
      Result.AddPair('data',data);
      Logger.Log(3,'---TApiService.GetComplaintMap End (returned='+emitted.ToString+')');
    except
      on E: Exception do
      begin
        FreeAndNil(data);
        Logger.Log(1,'GetComplaintMap error: '+E.Message);
        raise EXDataHttpException.Create(500,'Failed to load complaint map');
      end;
    end;
  finally
    UnitsByComplaintMap.Free;
  end;
end;




function TApiService.GetUnitMap: TJSONObject;
var
  data: TJSONArray;
begin
  Logger.Log(3, '---TApiService.GetUnitMap initiated');

  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  data := TJSONArray.Create;
  try
    with ApiDB.uqMapUnits do
    begin
      Open;
      First;
      while not Eof do
      begin
        // skip rows without coordinates
        if (not FieldByName('GPS_LATITUDE').IsNull) and (not FieldByName('GPS_LONGITUDE').IsNull) then
        begin
          var item := TJSONObject.Create;
          item.AddPair('UnitId', ApiDB.uqMapUnitsUNITID.AsString);
          item.AddPair('UnitName', ApiDB.uqMapUnitsUNITNAME.AsString);
          item.AddPair('District', ApiDB.uqMapUnitsUNIT_DISTRICT.AsString);
          item.AddPair('Lat', TJSONNumber.Create(ApiDB.uqMapUnitsGPS_LATITUDE.AsFloat));
          item.AddPair('Lng', TJSONNumber.Create(ApiDB.uqMapUnitsGPS_LONGITUDE.AsFloat));
          data.AddElement(item);
        end;
        Next;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(data.Count));
    Result.AddPair('returned', TJSONNumber.Create(data.Count));
    Result.AddPair('data', data);
  except
    data.Free;
    Logger.Log(3, '---TApiService.GetUnitMap End (error)');
    raise EXDataHttpException.Create(500, 'Failed to load unit map');
  end;

  Logger.Log(3, '---TApiService.GetUnitMap End');
end;


function TApiService.GetComplaintList: TJSONObject;
var
  data: TJSONArray;
  lastDistrict: string;
begin
  Logger.Log(3, '---TApiService.GetComplaintList initiated');

  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  data := TJSONArray.Create;
  try
    lastDistrict := '';
    with ApiDB.uqComplaintList do
    begin
      Open;
      (FieldByName('DATEREPORTED') as TDateTimeField).DisplayFormat := 'yyyy-mm-dd hh:nn:ss';
      First;
      while not Eof do
      begin
        var status: string;
        if not FieldByName('DATECLEARED').IsNull then
          status := 'Cleared'
        else if not FieldByName('DATEARRIVED').IsNull then
          status := 'AtScene'
        else if not FieldByName('DATEDISPATCHED').IsNull then
          status := 'Dispatched'
        else
          status := 'Pending';

        var item := TJSONObject.Create;

        var curDistrict := ApiDB.uqComplaintListDISPATCHDISTRICT.AsString;
        if not SameText(curDistrict, lastDistrict) then
          item.AddPair('DistrictHeader', curDistrict);
        lastDistrict := curDistrict;

        var districtSector := ApiDB.uqComplaintListDISTRICT_DESC.AsString + ApiDB.uqComplaintListSECTOR_DESC.AsString;
        item.AddPair('DistrictSector', districtSector);

        var colorVal := ApiDB.uqComplaintListPRIORITY_COLOR.AsInteger;
        item.AddPair('PriorityColor', '#' + IntToHex(colorVal and $FFFFFF, 6));

        if (colorVal and $FFFFFF) = $0000FF then
          item.AddPair('PriorityTextColor', '#FFFFFF')
        else
          item.AddPair('PriorityTextColor', '#000000');

        item.AddPair('ComplaintId', ApiDB.uqComplaintListCOMPLAINTID.AsString);
        item.AddPair('Complaint', ApiDB.uqComplaintListcomplaintNumber.AsString);
        item.AddPair('Agency', ApiDB.uqComplaintListAGENCY.AsString);
        item.AddPair('Priority', ApiDB.uqComplaintListPRIORITY.AsString);
        item.AddPair('DispatchCodeDesc', ApiDB.uqComplaintListDISPATCH_CODE_DESC.AsString);
        item.AddPair('Address', ApiDB.uqComplaintListADDRESS.AsString);
        item.AddPair('CFSId', ApiDB.uqComplaintListCFSID.AsString);
        item.AddPair('Status', status);
        item.AddPair('DispatchDistrict', curDistrict);
        item.AddPair('DateReported', ApiDB.uqComplaintListDATEREPORTED.AsString);

        data.AddElement(item);
        Next;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(data.Count));
    Result.AddPair('returned', TJSONNumber.Create(data.Count));
    Result.AddPair('data', data);
  except
    data.Free;
    Logger.Log(3, '---TApiService.GetComplaintList End (error)');
    raise EXDataHttpException.Create(500, 'Failed to load complaints list');
  end;

  Logger.Log(3, '---TApiService.GetComplaintList End');
end;


function TApiService.GetUnitList: TJSONObject;
var
  data: TJSONArray;
  lastDistrict: string;
begin
  Logger.Log(3, '---TApiService.GetUnitList initiated');

  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);

  data := TJSONArray.Create;
  try
    lastDistrict := '';
    with ApiDB.uqUnitList do
    begin
      Open;
      First;
      while not Eof do
      begin
        var item := TJSONObject.Create;


        // Group header: show once when district changes
        var curDistrict := ApiDB.uqUnitListDISTRICT_DESC.AsString;
        var header := IfThen(curDistrict <> '', curDistrict + ' District', '');
        if (header <> '') and not SameText(header, lastDistrict) then
          item.AddPair('DistrictHeader', header);
        lastDistrict := header;


        // Core unit identity
        item.AddPair('UnitId', ApiDB.uqUnitListUNITID.AsString);
        item.AddPair('UnitName', ApiDB.uqUnitListUNITNAME.AsString);
        item.AddPair('CarNumberDesc', ApiDB.uqUnitListCARNUMBER_DESC.AsString);
        item.AddPair('District', curDistrict);
        item.AddPair('Sector', ApiDB.uqUnitListSECTOR_DESC.AsString);
        item.AddPair('CallType', ApiDB.uqUnitListCALL_TYPE.AsString);


        // Current assignment (if any)
        item.AddPair('Location', ApiDB.uqUnitListLOCATION.AsString);
        item.AddPair('Complaint', ApiDB.uqUnitListCOMPLAINT.AsString);

        // Status: default to "Available" when no active CFS row
       var statusDesc := ApiDB.uqUnitListUNIT_STATUS_DESC.AsString;
       if statusDesc = '' then
         statusDesc := 'Available';
       item.AddPair('Status', statusDesc);

        // Officers (LAST, FIRST [MI])
        var o1 := Trim(ApiDB.uqUnitListOFFICER1_LAST_NAME.AsString);
        var f1 := Trim(ApiDB.uqUnitListOFFICER1_FIRST_NAME.AsString);
        var m1 := Trim(ApiDB.uqUnitListOFFICER1_MI.AsString);
        if o1 <> '' then
        begin
          if f1 <> '' then o1 := o1 + ', ' + f1;
          if m1 <> '' then o1 := o1 + ' ' + m1;
          item.AddPair('Officer1', o1);
        end;

        var o2 := Trim(ApiDB.uqUnitListOFFICER2_LAST_NAME.AsString);
        var f2 := Trim(ApiDB.uqUnitListOFFICER2_FIRST_NAME.AsString);
        var m2 := Trim(ApiDB.uqUnitListOFFICER2_MI.AsString);
        if o2 <> '' then
        begin
          if f2 <> '' then o2 := o2 + ', ' + f2;
          if m2 <> '' then o2 := o2 + ' ' + m2;
          item.AddPair('Officer2', o2);
        end;

        data.AddElement(item);
        Next;
      end;
    end;

    Result.AddPair('count', TJSONNumber.Create(data.Count));
    Result.AddPair('returned', TJSONNumber.Create(data.Count));
    Result.AddPair('data', data);
  except
    data.Free;
    Logger.Log(3, '---TApiService.GetUnitList End (error)');
    raise EXDataHttpException.Create(500, 'Failed to load units list');
  end;

  Logger.Log(3, '---TApiService.GetUnitList End');
end;



function TApiService.GetComplaintDetails(const ComplaintId: string): TJSONObject;
var
  obj: TJSONObject;
begin
  Logger.Log(3,'---TApiService.GetComplaintDetails initiated: '+ComplaintId);
  Result := TJSONObject.Create;
  TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
  try
    with ApiDB.uqComplaintDetails do
    begin
      ParamByName('COMPLAINTID').AsString := ComplaintId;
      Open;
      try
        if Eof then raise EXDataHttpException.Create(404,'Complaint not found');

        obj := TJSONObject.Create;
        obj.AddPair('ComplaintId',FieldByName('COMPLAINTID').AsString);
        obj.AddPair('CFSId',FieldByName('CFSID').AsString);
        obj.AddPair('Complaint',FieldByName('COMPLAINT').AsString);
        obj.AddPair('Priority',FieldByName('PRIORITY').AsString);
        obj.AddPair('DispatchCode',FieldByName('DISPATCHCODE').AsString);
        obj.AddPair('DispatchCodeDesc',FieldByName('DISPATCH_CODE_DESC').AsString);
        obj.AddPair('DispatchDistrict',FieldByName('DISPATCHDISTRICT').AsString);
        obj.AddPair('Address',FieldByName('ADDRESS').AsString);

        if FieldByName('DATEREPORTED').IsNull
          then obj.AddPair('DateReported','')
          else obj.AddPair('DateReported',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATEREPORTED') as TDateTimeField).AsDateTime));
        if FieldByName('DATERECEIVED').IsNull
          then obj.AddPair('DateReceived','')
          else obj.AddPair('DateReceived',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATERECEIVED') as TDateTimeField).AsDateTime));
        if FieldByName('DATEDISPATCHED').IsNull
          then obj.AddPair('DateDispatched','')
          else obj.AddPair('DateDispatched',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATEDISPATCHED') as TDateTimeField).AsDateTime));
        if FieldByName('DATERESPONDED').IsNull
          then obj.AddPair('DateResponded','')
          else obj.AddPair('DateResponded',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATERESPONDED') as TDateTimeField).AsDateTime));
        if FieldByName('DATEARRIVED').IsNull
          then obj.AddPair('DateArrived','')
          else obj.AddPair('DateArrived',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATEARRIVED') as TDateTimeField).AsDateTime));
        if FieldByName('DATECLEARED').IsNull
          then obj.AddPair('DateCleared','')
          else obj.AddPair('DateCleared',FormatDateTime('yyyy-mm-dd hh:nn:ss',(FieldByName('DATECLEARED') as TDateTimeField).AsDateTime));

        Result.AddPair('data',obj);
      finally
        Close;
      end;
    end;
  except
    on E: EXDataHttpException do
    begin
      Logger.Log(3,'---TApiService.GetComplaintDetails not found');
      raise;
    end;
    on E: Exception do
    begin
      Logger.Log(3,'---TApiService.GetComplaintDetails error: '+E.Message);
      raise EXDataHttpException.Create(500,'Failed to load complaint details');
    end;
  end;
  Logger.Log(3,'---TApiService.GetComplaintDetails End');
end;




initialization
  RegisterServiceType(TApiService);

end.

