package ru.nbch.credit_tracker.service.signal;

import jakarta.xml.bind.DatatypeConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import ru.nbch.credit_tracker.config.settings.CreditTrackerSettings;
import ru.nbch.credit_tracker.entities.external_response.errors.SgnlReport;
import ru.nbch.credit_tracker.entities.external_response.monitoring_report_file.SgnlReportMonitorigSetupError;
import ru.nbch.credit_tracker.entities.external_response.online_report.SgnlOnlineReport;
import ru.nbch.credit_tracker.service.SignalService;
import ru.nbch.credit_tracker.service.xml.jaxb.IXmlProcessor;
import ru.nbch.credit_tracker.service.xml.stax.StaxXmlProcessor;
import ru.nbch.credit_tracker.utils.CTUtil;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.UUID;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

@Slf4j
@Service
public class SignalRestService {

    private static String UPDATE_URI = "update";
    private static String DELETE_URI = "delete";
    private static String GET_REPORTS_URI = "onlinereport";
    private static String GET_REPORT_URI = "report";
    private static String DOWNLOAD_REPORT_URI = "download";
    private static String DOWNLOAD_ONLINE_REPORT_URI = "onlinedownload";

    private final RestTemplate restTemplate;
    private final IXmlProcessor xmlProcessor;
    private final StaxXmlProcessor staxProcessor;
    private final SignalService signalService;
    private final String authToken;

    private final CreditTrackerSettings config;

    public SignalRestService(@Qualifier(value = "restTemplate") RestTemplate restTemplate,
                             IXmlProcessor xmlProcessor,
                             StaxXmlProcessor staxProcessor,
                             SignalService signalService,
                             CreditTrackerSettings config) throws Exception {
        this.restTemplate = restTemplate;
        this.xmlProcessor = xmlProcessor;
        this.staxProcessor = staxProcessor;
        this.signalService = signalService;
        this.config = config;
        this.authToken = buildToken(config.getSignalsToken(), config.getSignalsuser());
    }

    /**
     * Ставит пакет на мониторинг в официальный сервис сигналов
     * @return true - если пакет успешно поставился на мониторинг. false - в противном случае
     */
    public boolean setupMonitoring(Integer packageId) throws Exception {
        String path = signalService.writeSignals(packageId);
        Resource request = compressFile(path);

        MultiValueMap<String, Resource> body = new LinkedMultiValueMap<>();
        body.add("file", request);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        headers.setBasicAuth(authToken);

        HttpEntity<MultiValueMap<String, Resource>> requestEntity = new HttpEntity<>(body, headers);

        ResponseEntity<String> response = restTemplate.postForEntity(UPDATE_URI, requestEntity, String.class);

        if (response.getStatusCode().value() == 302) {
            // good response
            CTUtil.removeFile(path);
            CTUtil.removeFile(path + ".gz");
            return true;
        } else if (response.getStatusCode().value() == 200 && response.getBody() != null) {
            SgnlReport error = staxProcessor.read(response.getBody(), SgnlReport.class);
            if (error.getError().getDescription() != null) {
                log.error("Ошибка при постановке пакета на мониторинг в официальный сервис сигналов: {}", error.getError().getDescription());
            } else {
                log.error("неизвестная ошибка при постановке пакета на мониторинг в официальный сервис сигналов: {}", response.getBody());
            }
            CTUtil.removeFile(path);
            CTUtil.removeFile(path + ".gz");
            return false;
        }
        return false;
    }

    /**
     * @return имя файла с ошибками по поставленному на мониторинг отчету
     */
    public String getErrorFileName(Integer packageId, String packCode) {
        String url = GET_REPORT_URI + "?packCode=" + packCode;

        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth(authToken);

        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(headers);

        ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                requestEntity,
                String.class
        );

        String answer = response.getBody();

        SgnlReport error = staxProcessor.read(answer, SgnlReport.class);
        if (error.getError().getDescription() != null) {
            log.error("Ошибка при получении имени файла ошибки из сервиса НБКИ. packCode={}. Описание: {}", packCode, error.getError().getDescription());
            return null;
        }
        try {
            SgnlReportMonitorigSetupError report = xmlProcessor.read(answer, SgnlReportMonitorigSetupError.class);
            if (report.getOperationReport().getErrorsForNextReport() != null) {
                String fileName = report.getOperationReport().getErrorsForNextReport().getFilename();
                log.error("Найдены ошибки по пакету. packCode={}, packageId={}, fileName = {}", packCode, packageId, fileName);
                return fileName;
            } else {
                log.info("Пакет был поставлен на мониторинг без ошибок. packCode={}, packageId={}", packCode, packageId);
            }
        } catch (Exception e) {
            log.error("Ошибка при получении имени файла ошибки из сервиса НБКИ. Код ошибки: {} packCode={}", response.getStatusCode().value(), packCode);
        }

        return null;
    }

    /**
     * Загружает .gz отчет ошибок из официального сервиса сигналов, разархивирует в tmp_folder
     *
     * @return путь к разархивированному файлу. Пример: <loadDir>/tmp_folder/<package_id>/0001XX_MY_BATCH_223112.1.errors.xml.gz
     */
    public String loadErrorFile(Integer packageId, String fileName) {
        String url = DOWNLOAD_REPORT_URI + "?fNameRequest=" + fileName;

        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth(authToken);

        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(headers);

        ResponseEntity<byte[]> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                requestEntity,
                byte[].class
        );

        return unzipReport(response, packageId, fileName);
    }

    /**
     * Удаляет пакет с мониторинга
     */
    public void deletePackage(String username, String packageName) {
        String packCode = username.substring(0, 6) + "_" + packageName;
        String url = DELETE_URI + "?packCode=" + packCode;
        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth(authToken);

        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(headers);

        ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                requestEntity,
                String.class
        );
        String answer = response.getBody();

        SgnlReport error = staxProcessor.read(answer, SgnlReport.class);
        if (error.getError().getDescription() != null) {
            log.error("Ошибка при удалении пакета из сервиса НБКИ. packCode={}. Описание: {}", packCode, error.getError().getDescription());
        }
    }

    /**
     * @return Список отчетов, по которым сработали сигналы из официального сервиса сигналов
     */
    public SgnlOnlineReport getReports(String packCode) {
        String url = GET_REPORTS_URI + "?packCode=" + packCode;

        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth(authToken);

        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(headers);

        ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                requestEntity,
                String.class
        );

        String answer = response.getBody();

        SgnlReport error = staxProcessor.read(answer, SgnlReport.class);
        if (error.getError().getDescription() != null) {
            log.error("Ошибка при получении пакета из сервиса НБКИ. packCode={}. Описание: {}", packCode, error.getError().getDescription());
            return null;
        }
        try {
            return xmlProcessor.read(answer, SgnlOnlineReport.class);
        } catch (Exception e) {
            log.error("Ошибка при получении пакета из сервиса НБКИ. Код ошибки: {} packCode={}", response.getStatusCode().value(), packCode);
            return null;
        }
    }

    /**
     * Загружает .gz отчет из официального сервиса сигналов, разархивирует в tmp_folder
     *
     * @return путь к разархивированному файлу. Пример: <loadDir>/tmp_folder/<package_id>/0001XX_MY_BATCH_204.1.online.2025-07-08T20-00-46.xml.gz.xml
     */
    public String downloadReport(Integer packageId, String reportName) {
        String url = DOWNLOAD_ONLINE_REPORT_URI + "?fNameReport=" + reportName;

        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth(authToken);

        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(headers);

        ResponseEntity<byte[]> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                requestEntity,
                byte[].class
        );

        return unzipReport(response,packageId, reportName);
    }

    private String unzipReport(ResponseEntity<byte[]> response, Integer packageId, String reportName) {
        boolean isGzipped = response.getHeaders().getContentType() != null
                && response.getHeaders().getContentType().toString().equals("application/x-gzip");

        if (!isGzipped && isXmlError(response.getBody())) {
            SgnlReport error = staxProcessor.read(response.getBody(), SgnlReport.class);
            if (error.getError().getDescription() != null) {
                log.error("Ошибка при скачивание отчета. fNameReport={}. Описание: {}", reportName, error.getError().getDescription());
                return null;
            }
            log.error("Неизвестная ошибка при скачивание отчета. fNameReport={}", reportName);
            return null;
        } else {
            String folderPath = config.getLoadDir() + File.separator + "tmp_folder" + File.separator;
            String filePath = folderPath + UUID.randomUUID() + reportName + ".xml";
            CTUtil.createFolder(folderPath);

            try (InputStream in = new GZIPInputStream(new ByteArrayInputStream(response.getBody()));
                 OutputStream out = Files.newOutputStream(Path.of(filePath))) {

                byte[] buffer = new byte[8 * 1024 * 1024];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) > 0) {
                    out.write(buffer, 0, bytesRead);
                }

                return filePath;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                return null;
            }
        }
    }

    private String buildToken(String signalsToken, String signalsuser) throws Exception {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] messageDigest = md.digest((signalsuser + "|" + signalsToken).getBytes(StandardCharsets.UTF_8));
            String hash = DatatypeConverter.printHexBinary(messageDigest).toLowerCase();
            Base64.Encoder encoder = Base64.getEncoder();
            return encoder.encodeToString((signalsuser + ":" + hash).getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            log.error("Error building token", e);
            throw new Exception("Error building token", e);
        }
    }

    public Resource compressFile(String path) throws Exception {
        try (InputStream in = Files.newInputStream(Path.of(path));
             OutputStream out = Files.newOutputStream(Path.of(path + ".gz"));
             GZIPOutputStream gzipOut = new GZIPOutputStream(out)) {

            byte[] buffer = new byte[8 * 1024 * 1024];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) > 0) {
                gzipOut.write(buffer, 0, bytesRead);
            }
            gzipOut.finish();

            return new FileUrlResource(path + ".gz");
        }
    }

    private boolean isXmlError(byte[] data) {
        if (data == null || data.length < 10) return false;
        String start = new String(data, 0, 10).trim();
        return start.startsWith("<?xml");
    }
}
