content: /*
   * Copyright 2018-2024 adorsys GmbH & Co KG
   *
   * This program is free software: you can redistribute it and/or modify it
   * under the terms of the GNU Affero General Public License as published
   * by the Free Software Foundation, either version 3 of the License, or (at
   * your option) any later version. This program is distributed in the hope that
   * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
   * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   * See the GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with this program. If not, see https://www.gnu.org/licenses/.
   *
   * This project is also available under a separate commercial license. You can
   * contact us at sales@adorsys.com.
   */
  
  package de.adorsys.psd2.xs2a.web.controller.psd2;
  
  import de.adorsys.psd2.api.AccountApi;
  import de.adorsys.psd2.xs2a.core.ais.BookingStatus;
  import de.adorsys.psd2.xs2a.core.error.MessageError;
  import de.adorsys.psd2.xs2a.core.error.MessageErrorCode;
  import de.adorsys.psd2.xs2a.domain.ResponseObject;
  import de.adorsys.psd2.xs2a.domain.Transactions;
  import de.adorsys.psd2.xs2a.domain.account.*;
  import de.adorsys.psd2.xs2a.service.ais.AccountDetailsService;
  import de.adorsys.psd2.xs2a.service.ais.AccountListService;
  import de.adorsys.psd2.xs2a.service.ais.BalanceService;
  import de.adorsys.psd2.xs2a.service.ais.TransactionService;
  import de.adorsys.psd2.xs2a.service.mapper.AccountModelMapper;
  import de.adorsys.psd2.xs2a.service.mapper.ResponseMapper;
  import de.adorsys.psd2.xs2a.service.mapper.TransactionModelMapper;
  import de.adorsys.psd2.xs2a.service.mapper.psd2.ResponseErrorMapper;
  import de.adorsys.psd2.xs2a.web.controller.util.RequestUriHandler;
  import de.adorsys.psd2.xs2a.web.error.TppErrorMessageWriter;
  import de.adorsys.psd2.xs2a.web.filter.TppErrorMessage;
  import jakarta.servlet.http.HttpServletRequest;
  import jakarta.servlet.http.HttpServletResponse;
  import lombok.AllArgsConstructor;
  import lombok.extern.slf4j.Slf4j;
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.lang3.BooleanUtils;
  import org.springframework.http.MediaType;
  import org.springframework.http.ResponseEntity;
  import org.springframework.web.bind.annotation.GetMapping;
  import org.springframework.web.bind.annotation.PathVariable;
  import org.springframework.web.bind.annotation.RequestHeader;
  import org.springframework.web.bind.annotation.RestController;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.time.LocalDate;
  import java.util.Optional;
  import java.util.UUID;
  
  import static de.adorsys.psd2.xs2a.core.domain.MessageCategory.ERROR;
  
  @SuppressWarnings("unchecked") // This class implements autogenerated interface without proper return values generated
  @Slf4j
  @RestController
  @AllArgsConstructor
  public class AccountController implements AccountApi {
  
      private final HttpServletRequest request;
      private final HttpServletResponse response;
      private final BalanceService balanceService;
      private final AccountListService accountListService;
      private final AccountDetailsService accountDetailsService;
      private final TransactionService transactionService;
      private final ResponseMapper responseMapper;
      private final AccountModelMapper accountModelMapper;
      private final TransactionModelMapper transactionModelMapper;
      private final ResponseErrorMapper responseErrorMapper;
      private final TppErrorMessageWriter tppErrorMessageWriter;
      private final RequestUriHandler requestUriHandler;
  
      @Override
      public ResponseEntity getAccountList(UUID xRequestID, String consentID, Boolean withBalance, String digest, String signature, byte[] tpPSignatureCertificate, String psUIPAddress, String psUIPPort, String psUAccept, String psUAcceptCharset, String psUAcceptEncoding, String psUAcceptLanguage, String psUUserAgent, String psUHttpMethod, UUID psUDeviceID, String psUGeoLocation) {
          ResponseObject<Xs2aAccountListHolder> accountList = accountListService.getAccountList(consentID, Optional.ofNullable(withBalance).orElse(false), requestUriHandler.trimEndingSlash(request.getRequestURI()));
          return accountList.hasError()
                     ? responseErrorMapper.generateErrorResponse(accountList.getError())
                     : responseMapper.ok(accountList, accountModelMapper::mapToAccountList);
      }
  
      @Override
      public ResponseEntity readAccountDetails(String accountId, UUID xRequestID, String consentID, Boolean withBalance, String digest, String signature, byte[] tpPSignatureCertificate, String psUIPAddress, String psUIPPort, String psUAccept, String psUAcceptCharset, String psUAcceptEncoding, String psUAcceptLanguage, String psUUserAgent, String psUHttpMethod, UUID psUDeviceID, String psUGeoLocation) {
          ResponseObject<Xs2aAccountDetailsHolder> accountDetails = accountDetailsService.getAccountDetails(consentID, accountId, Optional.ofNullable(withBalance).orElse(false), requestUriHandler.trimEndingSlash(request.getRequestURI()));
          return accountDetails.hasError()
                     ? responseErrorMapper.generateErrorResponse(accountDetails.getError())
                     : responseMapper.ok(accountDetails, accountModelMapper::mapToInlineResponse200);
      }
  
      @Override
      public ResponseEntity getBalances(String accountId, UUID xRequestID, String consentID, String digest, String signature, byte[] tpPSignatureCertificate, String psUIPAddress, String psUIPPort, String psUAccept, String psUAcceptCharset, String psUAcceptEncoding, String psUAcceptLanguage, String psUUserAgent, String psUHttpMethod, UUID psUDeviceID, String psUGeoLocation) {
          ResponseObject<Xs2aBalancesReport> balancesReport = balanceService.getBalancesReport(consentID, accountId, requestUriHandler.trimEndingSlash(request.getRequestURI()));
          return balancesReport.hasError()
                     ? responseErrorMapper.generateErrorResponse(balancesReport.getError())
                     : responseMapper.ok(balancesReport, accountModelMapper::mapToBalance);
      }
  
      @Override
      public ResponseEntity getTransactionList(String accountId, String bookingStatus, UUID xRequestID, String consentID, LocalDate dateFrom, LocalDate dateTo, String entryReferenceFrom, Boolean deltaList, Boolean withBalance, Integer pageIndex, Integer itemsPerPage, String digest, String signature, byte[] tpPSignatureCertificate, String psUIPAddress, String psUIPPort, String psUAccept, String psUAcceptCharset, String psUAcceptEncoding, String psUAcceptLanguage, String psUUserAgent, String psUHttpMethod, UUID psUDeviceID, String psUGeoLocation) {
          String requestUri = requestUriHandler.handleTransactionUri(request.getRequestURI(), bookingStatus, pageIndex);
  
          Xs2aTransactionsReportByPeriodRequest xs2aTransactionsReportByPeriodRequest = new Xs2aTransactionsReportByPeriodRequest(consentID, accountId, request.getHeader("accept"), BooleanUtils.isTrue(withBalance), dateFrom, dateTo, BookingStatus.forValue(bookingStatus), requestUri, entryReferenceFrom, deltaList, pageIndex, itemsPerPage);
          ResponseObject<Xs2aTransactionsReport> transactionsReport = transactionService.getTransactionsReportByPeriod(xs2aTransactionsReportByPeriodRequest);
  
          if (transactionsReport.hasError()) {
              return responseErrorMapper.generateErrorResponse(transactionsReport.getError());
          } else if (transactionsReport.getBody().isResponseContentTypeJson()) {
              return responseMapper.ok(transactionsReport, transactionModelMapper::mapToTransactionsResponse200Json);
          } else {
              return responseMapper.ok(transactionsReport, transactionModelMapper::mapToTransactionsResponseRaw);
          }
      }
  
      @GetMapping(value = "/v1/accounts/{account-id}/transactions/download/{download-id}")
      public void downloadTransactions(@RequestHeader("Consent-ID") String consentId,
                                       @PathVariable("account-id") String accountId,
                                       @PathVariable("download-id") String downloadId) {
          ResponseObject<Xs2aTransactionsDownloadResponse> downloadTransactionsResponse = transactionService.downloadTransactions(consentId, accountId, downloadId);
  
          if (downloadTransactionsResponse.hasError()) {
              flushResponseError(downloadTransactionsResponse.getError());
              return;
          }
  
          Xs2aTransactionsDownloadResponse responseBody = downloadTransactionsResponse.getBody();
          Integer dataSizeBytes = responseBody.getDataSizeBytes();
          String dataFileName = responseBody.getDataFileName();
  
          try (InputStream transactions = responseBody.getTransactionStream()) {
              IOUtils.copy(transactions, response.getOutputStream(), 4096);
              response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
              response.addHeader("Content-Disposition", resolveContentDisposition(dataFileName));
              if (dataSizeBytes != null) {
                  response.setContentLength(dataSizeBytes);
              }
              response.flushBuffer();
          } catch (IOException e) {
              log.info("Consent-ID: [{}], Account-ID: [{}]. Download-ID [{}]. Download transactions failed: IOException occurred in downloadTransactions controller.",
                       consentId, accountId, downloadId);
              flushResponseError(new TppErrorMessage(ERROR, MessageErrorCode.INTERNAL_SERVER_ERROR));
          }
      }
  
      @Override
      public ResponseEntity getTransactionDetails(String accountId, String resourceId, UUID xRequestID, String consentID, String digest, String signature, byte[] tpPSignatureCertificate, String psUIPAddress, String psUIPPort, String psUAccept, String psUAcceptCharset, String psUAcceptEncoding, String psUAcceptLanguage, String psUUserAgent, String psUHttpMethod, UUID psUDeviceID, String psUGeoLocation) {
          ResponseObject<Transactions> transactionDetails = transactionService.getTransactionDetails(consentID, accountId, resourceId, requestUriHandler.trimEndingSlash(request.getRequestURI()));
          return transactionDetails.hasError()
                     ? responseErrorMapper.generateErrorResponse(transactionDetails.getError())
                     : responseMapper.ok(transactionDetails, transactionModelMapper::mapToTransactionDetails);
  
      }
  
      private String resolveContentDisposition(String fileName) {
          return String.format("attachment; filename=%s", fileName == null ? System.currentTimeMillis() : fileName);
      }
  
      private void flushResponseError(TppErrorMessage errorMessage) {
          try {
              tppErrorMessageWriter.writeError(response, errorMessage);
              response.flushBuffer();
          } catch (IOException e) {
              log.info(" Writing to the httpServletResponse failed.");
          }
      }
  
      private void flushResponseError(MessageError messageError) {
          try {
              tppErrorMessageWriter.writeError(response, messageError);
              response.flushBuffer();
          } catch (IOException e) {
              log.info(" Writing to the httpServletResponse failed.");
          }
      }
  }
  