unit uEnviosClient;

{
  uEnviosClient.pas
  Factory de clientes de envíos multi-transportista.

  Descripción:
    La función TEnviosClientFactory.Create recibe la configuración cargada
    de ge_envios_config y devuelve la implementación concreta del cliente
    según el campo TipoApi (que a su vez viene de ge_envios_transportistas.tipo_api).

    Añadir soporte para un nuevo transportista = crear uEnviosXXXClient.pas
    e incluirlo en el case de la función Create. Los formularios de UI no
    necesitan ningún cambio.
}

interface

uses
  System.SysUtils, System.Classes, uEnviosTypes, uEnviosTipsaClient, uEnviosNacexClient;

type
  // -------------------------------------------------------------------------
  // Interfaz genérica que todo cliente de transportista debe implementar
  // -------------------------------------------------------------------------
  IEnviosClient = interface
    ['{A3B7C2D4-E5F6-4A8B-9C0D-1E2F3A4B5C6D}']
    function Login: Boolean;
    function GrabaEnvio(const AParams: TEnvioParams): TEnvioResult;
    function GrabaRecogida(const AParams: TRecogidaParams): TRecogidaResult;
    function GetEtiqueta(const ARef: string; APDF: Boolean): TBytes;
    function GetEstados(const ARef: string): TArray<TEstadoEnvio>;
    function AnulaEnvio(const ARef: string; out AMsg: string): Boolean;
    function ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string; AGirar180: Boolean): Boolean; overload;
    function ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string): Boolean; overload;
    function ImprimirEtiquetaTermica(const ARef: string; out AError: string): Boolean; overload;
  end;

  // -------------------------------------------------------------------------
  // Adaptador que envuelve TTipsaEnviosClient implementando IEnviosClient
  // -------------------------------------------------------------------------
  TTipsaClientAdapter = class(TInterfacedObject, IEnviosClient)
  private
    FClient: TTipsaEnviosClient;
  public
    constructor Create(const AConfig: TEnviosConfig);
    destructor Destroy; override;
    function Login: Boolean;
    function GrabaEnvio(const AParams: TEnvioParams): TEnvioResult;
    function GrabaRecogida(const AParams: TRecogidaParams): TRecogidaResult;
    function GetEtiqueta(const ARef: string; APDF: Boolean): TBytes;
    function GetEstados(const ARef: string): TArray<TEstadoEnvio>;
    function AnulaEnvio(const ARef: string; out AMsg: string): Boolean;
    function ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string; AGirar180: Boolean): Boolean; overload;
    function ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string): Boolean; overload;
    function ImprimirEtiquetaTermica(const ARef: string; out AError: string): Boolean; overload;
  end;

  // -------------------------------------------------------------------------
  // Adaptador que envuelve TNacexEnviosClient implementando IEnviosClient
  // -------------------------------------------------------------------------
  TNacexClientAdapter = class(TInterfacedObject, IEnviosClient)
  private
    FClient: TNacexEnviosClient;
  public
    constructor Create(const AConfig: TEnviosConfig);
    destructor Destroy; override;
    function Login: Boolean;
    function GrabaEnvio(const AParams: TEnvioParams): TEnvioResult;
    function GrabaRecogida(const AParams: TRecogidaParams): TRecogidaResult;
    function GetEtiqueta(const ARef: string; APDF: Boolean): TBytes;
    function GetEstados(const ARef: string): TArray<TEstadoEnvio>;
    function AnulaEnvio(const ARef: string; out AMsg: string): Boolean;
    function ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string; AGirar180: Boolean): Boolean; overload;
    function ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string): Boolean; overload;
    function ImprimirEtiquetaTermica(const ARef: string; out AError: string): Boolean; overload;
  end;

  // -------------------------------------------------------------------------
  // Factory: crea el cliente adecuado según AConfig.TipoApi
  // -------------------------------------------------------------------------
  TEnviosClientFactory = class
  public
    // Devuelve nil y LError <> '' si el tipo de API no está soportado
    class function Create(const AConfig: TEnviosConfig;
                          out LError: string): IEnviosClient;
  end;

  // -------------------------------------------------------------------------
  // Helper: carga un TEnviosConfig desde la BD dado un config_id
  // (requiere que dmgMain.dbConn esté abierta)
  // -------------------------------------------------------------------------
  function CargarEnviosConfig(AConfigId: Integer; out AConfig: TEnviosConfig): Boolean;

implementation

uses
  Winapi.Windows, Winapi.ShellAPI, Data.DB, FireDAC.Comp.Client, dmg_Main, uPdfRotateService;

// ---------------------------------------------------------------------------
function CargarEnviosConfig(AConfigId: Integer; out AConfig: TEnviosConfig): Boolean;
var
  Qry: TFDQuery;
begin
  Result := False;
  AConfig := Default(TEnviosConfig);

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text :=
      'SELECT ec.id, ec.empresa_id, ec.transportista_id, ec.entorno, ' +
      '       ec.cod_agencia, ec.cod_cliente, ec.password, ' +
      '       ec.cod_agencia_val, ec.cod_cliente_val, ec.password_val, ' +
      '       ec.nom_remitente, ec.dir_remitente, ec.pob_remitente, ' +
      '       ec.cp_remitente, ec.tlf_remitente, ec.email_remitente, ec.modelo_impresora, ec.serv_defecto, ' +
      '       ec.url_produccion, ec.url_pruebas, ' +
      '       t.nombre AS transp_nombre, t.tipo_api ' +
      'FROM ge_envios_config ec ' +
      'JOIN ge_envios_transportistas t ON t.id = ec.transportista_id ' +
      'WHERE ec.id = :id AND ec.activo = 1';
    Qry.ParamByName('id').AsInteger := AConfigId;
    Qry.Open;

    if Qry.IsEmpty then Exit;

    AConfig.ConfigId        := Qry.FieldByName('id').AsInteger;
    AConfig.TransportistaId := Qry.FieldByName('transportista_id').AsInteger;
    AConfig.NombreTransp    := Qry.FieldByName('transp_nombre').AsString;
    AConfig.TipoApi         := Qry.FieldByName('tipo_api').AsString;
    AConfig.TipoEnum        := TipoTransportistaFromStr(AConfig.TipoApi);
    AConfig.Entorno         := Qry.FieldByName('entorno').AsInteger <> 0;
    AConfig.UrlProduccion   := Qry.FieldByName('url_produccion').AsString;
    AConfig.UrlPruebas      := Qry.FieldByName('url_pruebas').AsString;

    if AConfig.Entorno then
    begin
      AConfig.CodAgencia    := Qry.FieldByName('cod_agencia_val').AsString;
      AConfig.CodCliente    := Qry.FieldByName('cod_cliente_val').AsString;
      AConfig.Password      := Qry.FieldByName('password_val').AsString;
    end
    else
    begin
      AConfig.CodAgencia    := Qry.FieldByName('cod_agencia').AsString;
      AConfig.CodCliente    := Qry.FieldByName('cod_cliente').AsString;
      AConfig.Password      := Qry.FieldByName('password').AsString;
    end;
    AConfig.NomRemitente    := Qry.FieldByName('nom_remitente').AsString;
    AConfig.DirRemitente    := Qry.FieldByName('dir_remitente').AsString;
    AConfig.PobRemitente    := Qry.FieldByName('pob_remitente').AsString;
    AConfig.CPRemitente     := Qry.FieldByName('cp_remitente').AsString;
    AConfig.TlfRemitente    := Qry.FieldByName('tlf_remitente').AsString;
    AConfig.EmailRemitente  := Qry.FieldByName('email_remitente').AsString;
    AConfig.ModeloImpresora := Qry.FieldByName('modelo_impresora').AsString;
    AConfig.ServDefecto     := Qry.FieldByName('serv_defecto').AsString;

    Result := True;
  finally
    Qry.Free;
  end;
end;

// ---------------------------------------------------------------------------
// Adaptador TIPSA
// ---------------------------------------------------------------------------
constructor TTipsaClientAdapter.Create(const AConfig: TEnviosConfig);
begin
  inherited Create;
  FClient := TTipsaEnviosClient.Create(AConfig);
end;

destructor TTipsaClientAdapter.Destroy;
begin
  FClient.Free;
  inherited;
end;

function TTipsaClientAdapter.Login: Boolean;
begin
  Result := FClient.Login;
end;

function TTipsaClientAdapter.GrabaEnvio(const AParams: TEnvioParams): TEnvioResult;
begin
  Result := FClient.GrabaEnvio(AParams);
end;

function TTipsaClientAdapter.GrabaRecogida(const AParams: TRecogidaParams): TRecogidaResult;
begin
  Result := FClient.GrabaRecogida(AParams);
end;

function TTipsaClientAdapter.GetEtiqueta(const ARef: string; APDF: Boolean): TBytes;
begin
  Result := FClient.GetEtiqueta(ARef, APDF);
end;

function TTipsaClientAdapter.GetEstados(const ARef: string): TArray<TEstadoEnvio>;
begin
  Result := FClient.GetEstados(ARef);
end;

function TTipsaClientAdapter.AnulaEnvio(const ARef: string; out AMsg: string): Boolean;
begin
  AMsg := 'El transportista TIPSA no soporta anulación automática de envíos desde la API.';
  Result := False;
end;

function TTipsaClientAdapter.ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string): Boolean;
begin
  Result := ImprimirEtiquetaTermica(ARef, AImpresora, AError, True);
end;

function TTipsaClientAdapter.ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string; AGirar180: Boolean): Boolean;
var
  LBytes: TBytes;
  LPdfPath, LPrinterName: string;
  LFS: TFileStream;
  LRet: NativeUInt;
begin
  Result := False;
  AError := '';
  LPrinterName := Trim(AImpresora);
  try
    LBytes := GetEtiqueta(ARef, True);
    if Length(LBytes) = 0 then
    begin
      AError := 'Tipsa no devolvió datos de etiqueta para la referencia ' + ARef + '.';
      Exit;
    end;

    LPdfPath := GetEnvironmentVariable('TEMP') + '\Etiqueta_Tipsa_' +
                StringReplace(ARef, '/', '_', [rfReplaceAll]) + '_' +
                FormatDateTime('yyyymmddhhnnss', Now) + '.pdf';
    LFS := TFileStream.Create(LPdfPath, fmCreate);
    try
      LFS.WriteBuffer(LBytes[0], Length(LBytes));
    finally
      LFS.Free;
    end;

    if AGirar180 then
      TPdfRotateService.RotarPdfArchivo180(LPdfPath);

    if LPrinterName <> '' then
    begin
      LRet := ShellExecute(0, 'printto', PChar(LPdfPath), PChar('"' + LPrinterName + '"'), nil, SW_HIDE);
      if LRet > 32 then
        Result := True
      else
        AError := 'Error al enviar la impresión a la impresora "' + LPrinterName + '".';
    end
    else
      Result := True;
  except
    on E: Exception do
      AError := E.Message;
  end;
end;

function TTipsaClientAdapter.ImprimirEtiquetaTermica(const ARef: string; out AError: string): Boolean;
begin
  Result := ImprimirEtiquetaTermica(ARef, '', AError, True);
end;

// ---------------------------------------------------------------------------
// Adaptador NACEX
// ---------------------------------------------------------------------------
constructor TNacexClientAdapter.Create(const AConfig: TEnviosConfig);
begin
  inherited Create;
  FClient := TNacexEnviosClient.Create(AConfig);
end;

destructor TNacexClientAdapter.Destroy;
begin
  FClient.Free;
  inherited;
end;

function TNacexClientAdapter.Login: Boolean;
begin
  Result := FClient.Login;
end;

function TNacexClientAdapter.GrabaEnvio(const AParams: TEnvioParams): TEnvioResult;
begin
  Result := FClient.GrabaEnvio(AParams);
end;

function TNacexClientAdapter.GrabaRecogida(const AParams: TRecogidaParams): TRecogidaResult;
begin
  Result := FClient.GrabaRecogida(AParams);
end;

function TNacexClientAdapter.GetEtiqueta(const ARef: string; APDF: Boolean): TBytes;
begin
  Result := FClient.GetEtiqueta(ARef, APDF);
end;

function TNacexClientAdapter.GetEstados(const ARef: string): TArray<TEstadoEnvio>;
begin
  Result := FClient.GetEstados(ARef);
end;

function TNacexClientAdapter.AnulaEnvio(const ARef: string; out AMsg: string): Boolean;
begin
  Result := FClient.AnulaEnvio(ARef, AMsg);
end;

function TNacexClientAdapter.ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string): Boolean;
begin
  Result := FClient.ImprimirEtiquetaIoNA(ARef, AImpresora, AError, True);
end;

function TNacexClientAdapter.ImprimirEtiquetaTermica(const ARef: string; const AImpresora: string; out AError: string; AGirar180: Boolean): Boolean;
begin
  Result := FClient.ImprimirEtiquetaIoNA(ARef, AImpresora, AError, AGirar180);
end;

function TNacexClientAdapter.ImprimirEtiquetaTermica(const ARef: string; out AError: string): Boolean;
begin
  Result := FClient.ImprimirEtiquetaIoNA(ARef, '', AError, True);
end;

// ---------------------------------------------------------------------------
// Factory
// ---------------------------------------------------------------------------
class function TEnviosClientFactory.Create(const AConfig: TEnviosConfig;
  out LError: string): IEnviosClient;
begin
  Result := nil;
  LError := '';

  case AConfig.TipoEnum of
    ttTIPSA:
      Result := TTipsaClientAdapter.Create(AConfig);

    ttNACEX:
      Result := TNacexClientAdapter.Create(AConfig);

    // Para añadir MRW en el futuro:
    // ttMRW:
    //   Result := TMRWClientAdapter.Create(AConfig);

    // Para añadir SEUR:
    // ttSEUR:
    //   Result := TSEURClientAdapter.Create(AConfig);
  else
    LError := Format(
      'El transportista "%s" (API: %s) no está soportado todavía.',
      [AConfig.NombreTransp, AConfig.TipoApi]);
  end;
end;

end.
