unit uSifWsClient;

interface

uses
  System.SysUtils, System.Classes, System.Net.HttpClient, System.Net.HttpClientComponent,
  System.Net.URLClient, uVeriFactuTypes;

type
  /// <summary>Respuesta parseada del Web Service de la AEAT</summary>
  TSifWsResponse = record
    HttpCode: Integer;
    Exito: Boolean;
    EstadoEnvio: TSifEstadoEnvio;
    CSV: string;
    CodigoError: string;
    DescripcionError: string;
    XmlRespuesta: string;
  end;

  /// <summary>
  /// Cliente de comunicaciones Web Service SOAP / HTTPS con la AEAT
  /// para la remision automatica de registros VERI*FACTU.
  /// </summary>
  TSifWsClient = class
  private
    FHttpClient: TNetHTTPClient;
    FTimeoutSegundos: Integer;
    function ParsearRespuestaSoap(const AXml: string; AHttpCode: Integer): TSifWsResponse;
    function ExtraerTag(const AXml, ATag: string): string;
  public
    constructor Create(ATimeoutSegundos: Integer = 30);
    destructor Destroy; override;

    /// <summary>Envia el sobre SOAP a la AEAT y devuelve la respuesta procesada</summary>
    function EnviarRegistroAlta(AModo: TSifModoOperacion; const AEndpointUrl, ASoapXml: string): TSifWsResponse;
  end;

implementation

constructor TSifWsClient.Create(ATimeoutSegundos: Integer);
begin
  inherited Create;
  FTimeoutSegundos := ATimeoutSegundos;
  FHttpClient := TNetHTTPClient.Create(nil);
  FHttpClient.ConnectionTimeout := FTimeoutSegundos * 1000;
  FHttpClient.ResponseTimeout := FTimeoutSegundos * 1000;
  FHttpClient.UserAgent := 'Mozilla/5.0 (compatible; GESTION_RAUL_ASENCIO_SIF/1.0; Windows)';
end;

destructor TSifWsClient.Destroy;
begin
  FHttpClient.Free;
  inherited Destroy;
end;

function TSifWsClient.ExtraerTag(const AXml, ATag: string): string;
var
  LOpenTag, LCloseTag: string;
  LPosOpen, LPosClose, LStart: Integer;
begin
  Result := '';
  LOpenTag := '<' + ATag + '>';
  LCloseTag := '</' + ATag + '>';

  LPosOpen := Pos(LowerCase(LOpenTag), LowerCase(AXml));
  if LPosOpen = 0 then
  begin
    // Probar con namespace tipo <sif:Tag>
    LOpenTag := ':' + ATag + '>';
    LPosOpen := Pos(LowerCase(LOpenTag), LowerCase(AXml));
    if LPosOpen > 0 then
      LStart := LPosOpen + Length(LOpenTag)
    else
      Exit;
  end
  else
    LStart := LPosOpen + Length(LOpenTag);

  LPosClose := Pos(LowerCase(LCloseTag), LowerCase(AXml));
  if LPosClose = 0 then
  begin
    LCloseTag := ':' + ATag + '>';
    LPosClose := Pos(LowerCase(LCloseTag), LowerCase(AXml), LStart);
    if LPosClose = 0 then
      Exit;
  end;

  Result := Trim(Copy(AXml, LStart, LPosClose - LStart));
end;

function TSifWsClient.ParsearRespuestaSoap(const AXml: string; AHttpCode: Integer): TSifWsResponse;
var
  LEstadoRegistro, LCSV, LCodError, LDescError: string;
begin
  Result.HttpCode := AHttpCode;
  Result.XmlRespuesta := AXml;
  Result.Exito := False;
  Result.EstadoEnvio := seErrorComunicacion;

  if (AHttpCode <> 200) and (AHttpCode <> 500) then
  begin
    Result.CodigoError := 'HTTP_' + IntToStr(AHttpCode);
    Result.DescripcionError := 'Error en conexion con el servidor de la AEAT (HTTP ' + IntToStr(AHttpCode) + ')';
    Exit;
  end;

  // Extraer campos principales de respuesta AEAT
  LEstadoRegistro := UpperCase(ExtraerTag(AXml, 'EstadoRegistro'));
  LCSV := ExtraerTag(AXml, 'CSV');
  LCodError := ExtraerTag(AXml, 'CodigoErrorRegistro');
  LDescError := ExtraerTag(AXml, 'DescripcionErrorRegistro');

  if LCodError = '' then
  begin
    LCodError := ExtraerTag(AXml, 'faultcode');
    LDescError := ExtraerTag(AXml, 'faultstring');
  end;

  Result.CSV := LCSV;
  Result.CodigoError := LCodError;
  Result.DescripcionError := LDescError;

  if (LEstadoRegistro = 'CORRECTO') or (LEstadoRegistro = 'ACEPTADO') then
  begin
    Result.Exito := True;
    Result.EstadoEnvio := seAceptado;
  end
  else if (LEstadoRegistro = 'ACEPTADOCONERRORES') then
  begin
    Result.Exito := True;
    Result.EstadoEnvio := seAceptadoConErrores;
  end
  else if (LEstadoRegistro = 'INCORRECTO') or (LEstadoRegistro = 'RECHAZADO') then
  begin
    Result.Exito := False;
    Result.EstadoEnvio := seRechazado;
  end
  else
  begin
    // Si viene CSV pero sin estado explícito
    if LCSV <> '' then
    begin
      Result.Exito := True;
      Result.EstadoEnvio := seAceptado;
    end
    else
    begin
      Result.Exito := False;
      Result.EstadoEnvio := seRechazado;
    end;
  end;
end;

function TSifWsClient.EnviarRegistroAlta(AModo: TSifModoOperacion; const AEndpointUrl, ASoapXml: string): TSifWsResponse;
var
  LHeaders: TNetHeaders;
  LStreamIn, LStreamOut: TStringStream;
  LResponse: IHTTPResponse;
begin
  Result.HttpCode := 0;
  Result.Exito := False;
  Result.EstadoEnvio := seErrorComunicacion;
  Result.XmlRespuesta := '';
  Result.CSV := '';
  Result.CodigoError := '';
  Result.DescripcionError := '';

  if AModo = sifNoVeriFactu then
  begin
    // En modo NO_VERIFACTU no se remite a la AEAT
    Result.Exito := True;
    Result.EstadoEnvio := seNoEnviado;
    Result.DescripcionError := 'Modo NO_VERIFACTU: Custodia local activada. No remitido a AEAT.';
    Exit;
  end;

  SetLength(LHeaders, 2);
  LHeaders[0] := TNameValuePair.Create('Content-Type', 'text/xml; charset=utf-8');
  LHeaders[1] := TNameValuePair.Create('SOAPAction', '""');

  LStreamIn := TStringStream.Create(ASoapXml, TEncoding.UTF8);
  LStreamOut := TStringStream.Create('', TEncoding.UTF8);
  try
    try
      LResponse := FHttpClient.Post(AEndpointUrl, LStreamIn, LStreamOut, LHeaders);
      Result := ParsearRespuestaSoap(LStreamOut.DataString, LResponse.StatusCode);
    except
      on E: Exception do
      begin
        Result.HttpCode := 0;
        Result.Exito := False;
        Result.EstadoEnvio := seErrorComunicacion;
        Result.CodigoError := 'COMM_ERROR';
        Result.DescripcionError := 'Error de comunicacion con AEAT: ' + E.Message;
      end;
    end;
  finally
    LStreamIn.Free;
    LStreamOut.Free;
  end;
end;

end.
