package com.ejie.ad74a.service;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
import org.odftoolkit.odfdom.doc.table.OdfTable;
import org.odftoolkit.odfdom.doc.table.OdfTableRow;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ejie.ad74a.dao.PagosDao;
import com.ejie.ad74a.dao.TarifasDao;
import com.ejie.ad74a.exceptions.Ad74aException;
import com.ejie.ad74a.exceptions.ManagerErrors;
import com.ejie.ad74a.model.Pagos;
import com.ejie.ad74a.model.Procesos;
import com.ejie.ad74a.model.Tarifas;
import com.ejie.ad74a.util.comun.UtilLiterales;
import com.ejie.ad74a.util.comun.UtilSeguridad;
import com.ejie.ad74a.util.comun.UtilidadesFecha;
import com.ejie.ad74a.util.constantes.EstadosProcesosBatchEnum;
import com.ejie.ad74a.util.constantes.MesesEnum;
import com.ejie.ad74a.util.constantes.ProcesosBatchEnum;
import com.ejie.x38.dto.JerarquiaDto;
import com.ejie.x38.dto.TableRequestDto;
import com.ejie.x38.dto.TableResponseDto;
import com.ejie.x38.dto.TableRowDto;
import com.ejie.x38.json.JSONArray;
import com.ejie.x38.json.JSONException;
import com.ejie.x38.json.JSONObject;
import com.ejie.x38.util.DateTimeManager;
import com.lowagie.text.Document;
import com.lowagie.text.Phrase;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;

/**
 * PagosServiceImpl generated by UDA, 15-sep-2023 12:25:35.
 * 
 * @author UDA
 */

@Service(value = "pagosService")
public class PagosServiceImpl implements PagosService {

	private static final int TRANSACTION_TIMEOUT = 9999999;

	@Autowired
	private ReloadableResourceBundleMessageSource messageSource;

//	@Autowired
//	private PagosPrestadorAsyncProcessService pagoPrestadorAsyncProcessService;

	@Autowired
	private PagosDao pagosDao;

//	@Autowired
//	private PrestadorServicioDao prestadorServicioDao;

	@Autowired
	private TarifasDao tarifasDao;

//	@Autowired
//	private SolicitantesDao solicitantesDao;
//
//	@Autowired
//	private PagosBeneficiarioDao pagosBeneficiarioDao;
//
//	@Autowired
//	private ValoracionesDao valoracionesDao;

	@Autowired
	private ProcesosService procesosService;

	/**
	 * Inserts a single row in the Pagos table.
	 *
	 * @param pagos Pagos
	 * @return Pagos
	 */
	@Transactional(rollbackFor = Throwable.class)
	public Pagos add(Pagos pagos) {
		if (pagos != null) {
			pagos.setUsuAlta74(UtilSeguridad.getNameUsuarioConectado());
			pagos.setUsuModif74(UtilSeguridad.getNameUsuarioConectado());
			pagos.setfAlta74(UtilidadesFecha.getFechaSistema());
			pagos.setfModif74(UtilidadesFecha.getFechaSistema());
		}
		return this.pagosDao.add(pagos);
	}

	/**
	 * Updates a single row in the Pagos table.
	 *
	 * @param pagos Pagos
	 * @return Pagos
	 */
	@Transactional(rollbackFor = Throwable.class)
	public Pagos update(Pagos pagos) {
		if (pagos != null) {
			pagos.setUsuModif74(UtilSeguridad.getUsuarioConectado().getUserName());
			pagos.setfModif74(UtilidadesFecha.getFechaSistema());
		}
		return this.pagosDao.update(pagos);
	}

	/**
	 * Finds a single row in the Pagos table.
	 *
	 * @param pagos Pagos
	 * @return Pagos
	 */
	public Pagos find(Pagos pagos) {
		return (Pagos) this.pagosDao.find(pagos);
	}

	/**
	 * Deletes a single row in the Pagos table.
	 *
	 * @param pagos Pagos
	 */
	@Transactional(rollbackFor = Throwable.class)
	public void remove(Pagos pagos) {
		this.pagosDao.remove(pagos);
	}

	/**
	 * Finds a list of rows in the Pagos table.
	 *
	 * @param pagos           Pagos
	 * @param tableRequestDto TableRequestDto
	 * @return List<Pagos>
	 */
	public List<Pagos> findAll(Pagos pagos, TableRequestDto tableRequestDto) {
		return (List<Pagos>) this.pagosDao.findAll(pagos, tableRequestDto);
	}

	/**
	 * Finds rows in the Pagos table using like.
	 *
	 * @param pagos           Pagos
	 * @param tableRequestDto TableRequestDto
	 * @param startsWith      Boolean
	 * @return List<Pagos>
	 */
	public List<Pagos> findAllLike(Pagos pagos, TableRequestDto tableRequestDto, Boolean startsWith) {
		return (List<Pagos>) this.pagosDao.findAllLike(pagos, tableRequestDto, startsWith);
	}

	/*
	 * OPERACIONES RUP_TABLE
	 */

	/**
	 * Removes rows from the Pagos table.
	 *
	 * @param tableRequestDto TableRequestDto
	 */
	@Transactional(rollbackFor = Throwable.class)
	public void removeMultiple(TableRequestDto tableRequestDto) {
		this.pagosDao.removeMultiple(tableRequestDto);
	}

	/**
	 * Filter method in the Pagos table.
	 *
	 * @param filterPagos     Pagos
	 * @param tableRequestDto TableRequestDto
	 * @param startsWith      Boolean
	 * @return TableResponseDto<Pagos>
	 */
	public TableResponseDto<Pagos> filter(Pagos filterPagos, TableRequestDto tableRequestDto, Boolean startsWith) {
		List<Pagos> listaPagos = this.pagosDao.findAllLike(filterPagos, tableRequestDto, false);
		Long recordNum = this.pagosDao.findAllLikeCount(filterPagos != null ? filterPagos : new Pagos(), false);

		TableResponseDto<Pagos> tableResponseDto = new TableResponseDto<Pagos>(tableRequestDto, recordNum, listaPagos);

		if (tableRequestDto.getMultiselection().getSelectedIds() != null) {
			List<TableRowDto<Pagos>> reorderSelection = this.pagosDao.reorderSelection(filterPagos, tableRequestDto,
					startsWith);
			tableResponseDto.setReorderedSelection(reorderSelection);
			tableResponseDto.addAdditionalParam("reorderedSelection", reorderSelection);
			tableResponseDto.addAdditionalParam("selectedAll", tableRequestDto.getMultiselection().getSelectedAll());
		}
		if (tableRequestDto.getSeeker().getSelectedIds() != null) {
			tableRequestDto.setMultiselection(tableRequestDto.getSeeker());
			List<TableRowDto<Pagos>> reorderSeeker = this.pagosDao.reorderSelection(filterPagos, tableRequestDto,
					startsWith);
			tableResponseDto.setReorderedSeeker(reorderSeeker);
			tableResponseDto.addAdditionalParam("reorderedSeeker", reorderSeeker);
		}
		return tableResponseDto;
	}

	/**
	 * Searches rows in the Pagos table.
	 *
	 * @param filterPagos     Pagos
	 * @param searchPagos     Pagos
	 * @param tableRequestDto TableRequestDto
	 * @param startsWith      Boolean
	 * @return List<TableRowDto<Pagos>>
	 */
	public List<TableRowDto<Pagos>> search(Pagos filterPagos, Pagos searchPagos, TableRequestDto tableRequestDto,
			Boolean startsWith) {
		return this.pagosDao.search(filterPagos, searchPagos, tableRequestDto, startsWith);
	}

	/**
	 * Reorder the selection made in Pagos table.
	 *
	 * @param filterPagos     Pagos
	 * @param tableRequestDto TableRequestDto
	 * @param startsWith      Boolean
	 * @return Object
	 */
	public Object reorderSelection(Pagos filterPagos, TableRequestDto tableRequestDto, Boolean startsWith) {
		return this.pagosDao.reorderSelection(filterPagos, tableRequestDto, startsWith);
	}

	/*
	 * OPERACIONES RUP_TABLE JERARQUIA
	 */

	/**
	 * Finder method in the hierarchical the Pagos table.
	 *
	 * @param filterPagos     Pagos
	 * @param tableRequestDto TableRequestDto
	 * @param startsWith      Boolean
	 * @return TableResponseDto<JerarquiaDto<Pagos>>
	 */
	public TableResponseDto<JerarquiaDto<Pagos>> jerarquia(Pagos filterPagos, TableRequestDto tableRequestDto,
			Boolean startsWith) {
		List<JerarquiaDto<Pagos>> listaPagos = this.pagosDao.findAllLikeJerarquia(filterPagos, tableRequestDto);
		Long recordNum = this.pagosDao.findAllLikeCountJerarquia(filterPagos, tableRequestDto);
		return new TableResponseDto<JerarquiaDto<Pagos>>(tableRequestDto, recordNum, listaPagos);
	}

	/**
	 * Finder method for siblings rows in the hierarchical the Pagos table.
	 *
	 * @param filterPagos     Pagos
	 * @param tableRequestDto TableRequestDto
	 * @return TableResponseDto<JerarquiaDto<Pagos>>
	 */
	public TableResponseDto<JerarquiaDto<Pagos>> jerarquiaChildren(Pagos filterPagos, TableRequestDto tableRequestDto) {
		TableResponseDto<JerarquiaDto<Pagos>> tableResponseDto = new TableResponseDto<JerarquiaDto<Pagos>>();
		tableResponseDto.addAdditionalParam(TableResponseDto.CHILDREN,
				this.pagosDao.findAllChild(filterPagos, tableRequestDto));
		return tableResponseDto;
	}

	/**
	 * Exporta Datos al clipBoard
	 *
	 */
	@Transactional(rollbackFor = Throwable.class)
	public List<Pagos> getMultiple(Pagos filterPagos, TableRequestDto tableRequestDto, Boolean startsWith) {
		return this.pagosDao.getMultiple(filterPagos, tableRequestDto, startsWith);
	}

	/**
	 * Devuelve un fichero en el formato deseado que contiene los datos exportados
	 * de la tabla.
	 *
	 * @param filterPagos     Pagos
	 * @param columns         String[]
	 * @param fileName        String
	 * @param sheetTitle      String
	 * @param reportsParams   ArrayList<?>
	 * @param tableRequestDto TableRequestDto
	 * @param request         HttpServletRequest
	 * @param response        HttpServletResponse
	 */
	@Override
	public void generateReport(Pagos filterPagos, String[] columns, String[] columnsName, String fileName,
			String sheetTitle, TableRequestDto tableRequestDto, HttpServletRequest request,
			HttpServletResponse response) {
		// Accede a la DB para recuperar datos
		List<Pagos> filteredData = getDataForReports(filterPagos, tableRequestDto);
		String extension = null;

		// Comprobar si las siguientes variables estan vacias, en caso de estarlo se las
		// asigna un valor generico
		fileName = (fileName != null && !fileName.isEmpty()) ? fileName : "report";
		sheetTitle = (sheetTitle != null && !sheetTitle.isEmpty()) ? sheetTitle : Pagos.class.getSimpleName();

		// Obtener el formato de fecha especifico del locale
		SimpleDateFormat formatter = DateTimeManager.getDateTimeFormat(request.getLocale());

		// Cuando no se definen columnas porque se quieren obtener todas
		if (columns == null) {
			Field[] fields = Pagos.class.getDeclaredFields();
			ArrayList<String> tempColumns = new ArrayList<String>();

			for (int i = 0; i < fields.length; i++) {
				try {
					String methodName = fields[i].getName();
					methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
					Pagos.class.getMethod("get" + methodName);
					tempColumns.add(fields[i].getName());
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				}

			}
			columns = tempColumns.toArray(new String[0]);
		}

		// si no se definen el nombre de las columnas , se dejan las de por defecto.
		if (columnsName == null) {
			columnsName = columns;
		}

		String servletPath = request.getServletPath();
		String reportType = null;
		if (servletPath.contains("/") && (servletPath.lastIndexOf("/") + 1 != servletPath.length())) {
			reportType = servletPath.substring(servletPath.lastIndexOf("/") + 1, servletPath.length());
		} else {
			reportType = servletPath.substring(0, servletPath.length());
		}

		if (reportType.equals("xlsReport")) {
			extension = ".xls";
			generateExcelReport(filteredData, columns, columnsName, fileName, sheetTitle, extension, formatter,
					response);
		} else if (reportType.equals("xlsxReport")) {
			extension = ".xlsx";
			generateExcelReport(filteredData, columns, columnsName, fileName, sheetTitle, extension, formatter,
					response);
		} else if (reportType.equals("pdfReport")) {
			extension = ".pdf";
			generatePDFReport(filteredData, columns, columnsName, fileName, response);
		} else if (reportType.equals("odsReport")) {
			extension = ".ods";
			generateODSReport(filteredData, columns, columnsName, fileName, sheetTitle, response);
		} else if (reportType.equals("csvReport")) {
			extension = ".csv";
			// Obtener idioma
			String language = request.getLocale().getLanguage();
			generateCSVReport(filteredData, columns, columnsName, fileName, sheetTitle, language, response);
		}

	}

	/**
	 * Devuelve un fichero excel que contiene los datos exportados de la tabla.
	 *
	 * @param filteredData List<Pagos>
	 * @param columns      String[]
	 * @param fileName     String
	 * @param sheetTitle   String
	 * @param extension    String
	 * @param formatter    SimpleDateFormat
	 * @param response     HttpServletResponse
	 */
	private void generateExcelReport(List<Pagos> filteredData, String[] columns, String[] columnsName, String fileName,
			String sheetTitle, String extension, SimpleDateFormat formatter, HttpServletResponse response) {
		try {
			// Creacion del Excel
			Workbook workbook = null;
			if (extension == ".xlsx") {
				workbook = new XSSFWorkbook();
				response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
			} else {
				workbook = new HSSFWorkbook();
				response.setContentType("application/vnd.ms-excel");
			}

			response.setHeader("Content-Disposition", "attachment; filename=" + fileName + extension + "");

			// Creacion de una hoja y asignacion de su nombre
			Sheet sheet = workbook.createSheet(sheetTitle);

			// Se crea una fuente para estilizar las cabeceras
			Font headerFont = workbook.createFont();
			headerFont.setBold(true);
			headerFont.setFontHeightInPoints((short) 12);

			// Se crea un CellStyle con la fuente
			CellStyle headerCellStyle = workbook.createCellStyle();
			headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
			headerCellStyle.setFont(headerFont);

			// Inicializar contador de filas
			int rowNumber = 0;

			// Se crea la fila para insertar los titulos de las columnas
			Row row = sheet.createRow(rowNumber++);

			// Añadir titulos
			for (int i = 0; i < columnsName.length; i++) {
				Cell cell = row.createCell(i);
				cell.setCellValue(columnsName[i]);
				cell.setCellStyle(headerCellStyle);
			}

			// CreationHelper ayudara a mantener la compatibilidad del DataFormat tanto si
			// se crea un .xls como un .xlsx
			CreationHelper createHelper = workbook.getCreationHelper();

			// Se crea un CellStyle para añadir el formateador de fechas
			CellStyle dateCellStyle = workbook.createCellStyle();
			dateCellStyle.setDataFormat(createHelper.createDataFormat().getFormat(formatter.toPattern()));

			// Añadir datos
			for (Pagos rowPagos : filteredData) {
				int cellNumber = 0;
				row = sheet.createRow(rowNumber++);

				// Se iteran las columnas y se insertan los datos respetando el orden que tenian
				// las columnas en la tabla
				for (String column : columns) {
					Cell cellPagos = row.createCell(cellNumber++);
					cellPagos.setCellValue(getCellValue(column, rowPagos));
				}
			}

			// Se adapta el ancho de las columnas al contenido
			for (int i = 0; i < columns.length; i++) {
				sheet.autoSizeColumn(i);
			}

			// Se añade el fichero excel al response
			workbook.write(response.getOutputStream());
			workbook.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Devuelve un fichero pdf que contiene los datos exportados de la tabla.
	 *
	 * @param filteredData List<Pagos>
	 * @param columns      String[]
	 * @param fileName     String
	 * @param response     HttpServletResponse
	 */
	private void generatePDFReport(List<Pagos> filteredData, String[] columns, String[] columnsName, String fileName,
			HttpServletResponse response) {
		try {
			// Se añade el fichero excel al response y se añade el contenido
			response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".pdf");
			response.setContentType("application/pdf");

			Document document = new Document();
			// Se añade el fichero pdf al response
			PdfWriter.getInstance(document, response.getOutputStream());

			document.open();

			PdfPTable table = new PdfPTable(columns.length);

			for (String column : columnsName) {
				PdfPCell header = new PdfPCell();
				header.setBorderWidth(2);
				header.setPhrase(new Phrase(column));
				table.addCell(header);
			}

			// Añadir datos
			for (Pagos rowPagos : filteredData) {
				// Se iteran las columnas y se insertan los datos respetando el orden que tenian
				// las columnas en la tabla
				for (String column : columns) {
					table.addCell(getCellValue(column, rowPagos));
				}
			}

			document.add(table);
			document.close();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Devuelve un fichero ods que contiene los datos exportados de la tabla.
	 *
	 * @param filteredData List<Pagos>
	 * @param columns      String[]
	 * @param fileName     String
	 * @param sheetTitle   String
	 * @param response     HttpServletResponse
	 */
	private void generateODSReport(List<Pagos> filteredData, String[] columns, String[] columnsName, String fileName,
			String sheetTitle, HttpServletResponse response) {
		try {
			// Se añade el fichero ods al response y se añade el contenido
			response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".ods");
			response.setContentType("application/vnd.oasis.opendocument.spreadsheet");

			OdfSpreadsheetDocument ods = OdfSpreadsheetDocument.newSpreadsheetDocument();
			ods.getOrCreateDocumentStyles();

			// Hay que eliminar la hoja que se genera por defecto
			ods.getTableByName("Sheet1").remove();

			// Hoja nueva
			OdfTable table = OdfTable.newTable(ods, filteredData.size() + 1, columns.length);
			table.setTableName(sheetTitle);

			// Inicializar contador de filas
			int rowNumber = 0;

			// Cabeceras
			OdfTableRow row = table.getRowByIndex(rowNumber++);
			for (int i = 0; i < columnsName.length; i++) {
				row.getCellByIndex(i).setStringValue(columnsName[i]);
			}

			// Añadir datos
			for (Pagos rowPagos : filteredData) {
				row = table.getRowByIndex(rowNumber++);
				int cellNumber = 0;

				// Se iteran las columnas y se insertan los datos respetando el orden que tenian
				// las columnas en la tabla
				for (String column : columns) {
					row.getCellByIndex(cellNumber++).setStringValue(getCellValue(column, rowPagos));
				}
			}

			// Se añade el fichero ods al response
			ods.save(response.getOutputStream());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Devuelve un fichero csv que contiene los datos exportados de la tabla.
	 *
	 * @param filteredData List<Pagos>
	 * @param columns      String[]
	 * @param fileName     String
	 * @param sheetTitle   String
	 * @param language     String
	 * @param response     HttpServletResponse
	 */
	private void generateCSVReport(List<Pagos> filteredData, String[] columns, String[] columnsName, String fileName,
			String sheetTitle, String language, HttpServletResponse response) {
		try {
			// Se añade el fichero excel al response y se añade el contenido
			response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".csv");
			response.setContentType("text/csv");

			// Separador de campos dependiendo del idioma
			String separator = ";";
			if (language.equals("en")) {
				separator = ",";
			}

			// Se añade el fichero csv al response
			OutputStream out = response.getOutputStream();
			// Añadir titulos
			boolean addTitles = true;

			// Añadir datos
			for (Pagos rowPagos : filteredData) {
				int cellNumber = 1;
				StringBuilder columnsTitles = new StringBuilder();
				StringBuilder row = new StringBuilder();

				// Se iteran las columnas y se insertan los datos respetando el orden que tenian
				// las columnas en la tabla
				for (String column : columns) {
					String cellValue = getCellValue(column, rowPagos);

					if (cellNumber < columns.length) {
						if (addTitles) {
							columnsTitles.append("\"");
							columnsTitles.append(column);
							columnsTitles.append("\"");
							columnsTitles.append(separator);
						}
						row.append("\"");
						row.append(cellValue);
						row.append("\"");
						row.append(separator);
						cellNumber++;
					} else {
						if (addTitles) {
							columnsTitles.append("\"");
							columnsTitles.append(column);
							columnsTitles.append("\"\n");
						}
						row.append("\"");
						row.append(cellValue);
						row.append("\"\n");
					}
				}

				if (addTitles) {
					out.write(columnsTitles.toString().getBytes());
					addTitles = false;
				}
				out.write(row.toString().getBytes());
			}
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Devuelve los datos recuperados de la DB.
	 *
	 * @param filterPagos     Pagos
	 * @param tableRequestDto TableRequestDto
	 */
	private List<Pagos> getDataForReports(Pagos filterPagos, TableRequestDto tableRequestDto) {
		if (tableRequestDto.getMultiselection().getSelectedAll()
				&& tableRequestDto.getMultiselection().getSelectedIds().isEmpty()) {
			if (filterPagos != null) {
				return this.pagosDao.findAllLike(filterPagos, tableRequestDto, false);
			} else {
				return this.pagosDao.findAll(new Pagos(), null);
			}
		} else {
			return this.pagosDao.getMultiple(filterPagos, tableRequestDto, false);
		}
	}

	/**
	 * Obtiene los valores de las celdas.
	 *
	 * @param column   String
	 * @param rowPagos Pagos
	 */
	private String getCellValue(String column, Pagos rowPagos) {
		String cellValue = "";
		try {
			cellValue = BeanUtils.getProperty(rowPagos, column) != null ? BeanUtils.getProperty(rowPagos, column) : "";
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}

		return cellValue;
	}

	/**
	 * Get all anyos.
	 *
	 * @return List<Long>
	 */
	@Override
	public List<Long> findAllAnyos() {
		return this.pagosDao.findAllAnyos();
	}

	/**
	 * Obtiene el ultimo año y mes de pagos.
	 * 
	 * @return Pago
	 */
	public Pagos obtenerUltimaFechaPago() {
		return this.pagosDao.obtenerUltimaFechaPago();
	}

	/**
	 * Obtener las tarifas por territorio.
	 * 
	 * @param pagos
	 * @return List<JSONObject>
	 */
	public List<JSONObject> findTarifasPorTerritorio(Pagos pagos) {
		return this.pagosDao.findTarifasPorTerritorio(pagos);
	}

	/**
	 * Obtener las tarifas diarias.
	 * 
	 * @param pagos
	 * @return List<JSONObject>
	 */
	public List<JSONObject> findTarifasDiarias(Pagos pagos) {
		/*-
		 * Hay que hacer una o varias querys para obtener los datos. De momento
		 * dejo un objeto de demo.
		 * pagosTarifasDiarias:{
		 * 			fechas:{
		 * 				0:{
		 * 					fechaFin:"20/11/2023",
		 * 					fechaInicio:"1/11/2023"
		 * 				},
		 * 				1:{
		 * 					fechaFin:"21/11/2023",
		 * 					fechaInicio:"30/11/2023"
		 * 				}
		 * 			},
		 * 			dependencias:{
		 * 				0:{
		 * 					dependencia:"Dependencia grado 1",
		 * 					precios:{
		 * 						0: 0.412,
		 *						1: 0.4
		 *					}
		 * 				},
		 * 				1:{
		 * 					dependencia:"Dependencia grado 2",
		 * 					precios:{
		 * 						0: 0.5,
		 *						1: 0.55
		 *					}
		 * 				}
		 * 			}
		 * }
		 */
		// Fechas inicio y fin de pantalla
		Calendar c1 = Calendar.getInstance();
		c1.set(Calendar.MONTH, Integer.parseInt(pagos.getMesPago74()) - 1);
		c1.set(Calendar.DAY_OF_MONTH, 1);
		c1.set(Calendar.YEAR, pagos.getAnyoPago74().intValue());
		c1.set(Calendar.HOUR_OF_DAY, 0);
		c1.set(Calendar.MINUTE, 0);
		c1.set(Calendar.SECOND, 0);
		c1.set(Calendar.MILLISECOND, 0);
		Date fechaIniCalculoPantalla = c1.getTime();

		Calendar c2 = c1;
		c2.set(Calendar.DAY_OF_MONTH, c2.getActualMaximum(Calendar.DAY_OF_MONTH));
		Date fechaFinCalculoPantalla = c2.getTime();

		// Obtener las tarifas, fechas de vigencia y el nivel de soporte
		List<Tarifas> tarifasConNivelSoporte = this.tarifasDao
				.findTarifasPorNivelSoporteEntreFechas(fechaIniCalculoPantalla, fechaFinCalculoPantalla);

		// Obtener intervalos de fechas
		List<Date> fechasInicio = new ArrayList<>();
		List<Date> fechasFin = new ArrayList<>();

		for (Tarifas fechasTarifa : tarifasConNivelSoporte) {
			// fechasInicio.add(fechasTarifa.getfVigenciaDesde76());
			// fechasFin.add(fechasTarifa.getfVigenciaHasta76());
			Date fI = fechasTarifa.getfVigenciaDesde76().compareTo(fechaIniCalculoPantalla) >= 0
					? fechasTarifa.getfVigenciaDesde76()
					: fechaIniCalculoPantalla;
			fechasInicio.add(fI);
			Date fF = fechasTarifa.getfVigenciaHasta76().compareTo(fechaFinCalculoPantalla) <= 0
					? fechasTarifa.getfVigenciaHasta76()
					: fechaFinCalculoPantalla;
			fechasFin.add(fF);
		}

		// Lista de fechas sin duplicados
		Set<Date> setWithoutDuplicatesFInicio = new LinkedHashSet<>(fechasInicio);
		Set<Date> setWithoutDuplicatesFFin = new LinkedHashSet<>(fechasFin);
		fechasInicio = new ArrayList<>(setWithoutDuplicatesFInicio);
		fechasFin = new ArrayList<>(setWithoutDuplicatesFFin);
		Collections.sort(fechasInicio);
		Collections.sort(fechasFin);

		// Crear lista con todos las fechas de inicio y de fin
//		SimpleDateFormat formato = new SimpleDateFormat("yyyy-MM-dd");
		List<JSONObject> listaFechas = new ArrayList<>();
		if (fechasInicio.size() == 1) {
			JSONObject fecha = new JSONObject();
			// fecha.put("fechaInicio", formato.format(fechaIniCalculoPantalla));
			fecha.put("fechaInicio", fechaIniCalculoPantalla);
			for (Date fechaFin : fechasFin) {
				if (fechaFin.compareTo(fechasInicio.get(0)) > 0) {
					fecha.put("fechaFin", fechaFin);
					break;
				}
			}

			listaFechas.add(fecha);
		} else {
			int idx = 0;
			for (Date fechaInicio : fechasInicio) {
				JSONObject fecha = new JSONObject();
//				fecha.put("fechaInicio", formato.format(fechaInicio));
				fecha.put("fechaInicio", fechaInicio);

				Date fechaFin = fechasFin.get(idx);
				fecha.put("fechaFin", fechaFin);
//				for (Date fechaFin : fechasFin) {
//					if (fechaFin.compareTo(fechaInicio) > 0) {
//						fecha.put("fechaFin", fechaFin);
//						break;
//					}
//				}
				listaFechas.add(fecha);
				idx++;
			}
		}
		// Ajustar la primera fecha de inicio al día 1 si no lo es
		if (!fechasInicio.isEmpty()) {
			Calendar calendar = Calendar.getInstance();

			Date primeraFechaInicio = fechasInicio.get(0);
			calendar.setTime(primeraFechaInicio);
			if (calendar.get(Calendar.DAY_OF_MONTH) != 1) {
				calendar.set(Calendar.DAY_OF_MONTH, 1);
				fechasInicio.set(0, calendar.getTime()); // Ajustar la primera fecha de la lista
			}
		}
		// Calculo de las dependencias con sus tarifas
		List<JSONObject> listaDependencias = new ArrayList<>();
		JSONObject dependencia = null;
		Long idNivelSoporte58Actual = null;
		List<BigDecimal> listaPrecios = null;
		for (Tarifas tarifaActual : tarifasConNivelSoporte) {
			// Se compara el nivel de soporte actual con el de la lista y si es distinto se
			// crea un nuevo objeto dependencia
			if (idNivelSoporte58Actual == null
					|| !tarifaActual.getMaestroNivSoporte().getIdNivelSoporte58().equals(idNivelSoporte58Actual)) {
				// Setear el nivel de soporte actual con el auxiliar
				idNivelSoporte58Actual = tarifaActual.getMaestroNivSoporte().getIdNivelSoporte58();

				// Crear objeto dependencia
				dependencia = new JSONObject();
				dependencia.put("dependencia", tarifaActual.getMaestroNivSoporte().getDesc());

				// Crear array con el tamaño de las fechas para rellenar con las tarifas
				listaPrecios = Arrays.asList(new BigDecimal[listaFechas.size()]);

				// Añadir la lista de precios al objeto dependencia
				dependencia.put("precios", listaPrecios);

				// Añadir la dependencia a la lista de dependencias
				listaDependencias.add(dependencia);
			}

			// Calcular la tarifas en base a fechas
			for (int i = 0; i < listaFechas.size(); i++) {
				JSONObject fecha = listaFechas.get(i);
				Date fIni = (Date) fecha.get("fechaInicio");
				Date fFin = fecha.isNull("fechaFin") ? null : (Date) fecha.get("fechaFin");
				// SI fVigenciaHasta > fFin && fVigenciaDesde < fFin
				// -> cumple
				// ELSE -> nocumple
//				if (fFin != null && tarifaActual.getfVigenciaHasta76().compareTo(fFin) >= 0
//						&& tarifaActual.getfVigenciaDesde76().compareTo(fFin) < 0) {
//					listaPrecios.set(i, tarifaActual.getTarifa76());
//				}

				if (fIni.compareTo(tarifaActual.getfVigenciaDesde76()) >= 0
						&& fFin.compareTo(tarifaActual.getfVigenciaHasta76()) <= 0) {
					listaPrecios.set(i, tarifaActual.getTarifa76());
				}

			}
		}

		// Crear el objeto para devolver a pantalla
		JSONArray arrayFechas = new JSONArray();
		SimpleDateFormat formato = new SimpleDateFormat("yyyy-MM-dd");
		for (int i = 0; i < listaFechas.size(); i++) {
			JSONObject fechas = listaFechas.get(i);
			fechas.put("fechaInicio", formato.format(fechas.get("fechaInicio")));
			fechas.put("fechaFin", formato.format(fechas.get("fechaFin")));
			arrayFechas.put(listaFechas.get(i));
		}

		JSONObject fechas = new JSONObject();
		try {
			fechas.put("fechas", arrayFechas);
		} catch (JSONException e) {
			ManagerErrors.manageException(e, "ERROR al crear el objeto fechas");
		}

		JSONArray arrayDependencias = new JSONArray();
		for (int i = 0; i < listaDependencias.size(); i++) {
			arrayDependencias.put(listaDependencias.get(i));
		}

		JSONObject dependencias = new JSONObject();
		try {
			dependencias.put("dependencias", arrayDependencias);
		} catch (JSONException e) {
			ManagerErrors.manageException(e, "ERROR al crear el objeto dependencias");
		}

		List<JSONObject> jsonObj = new ArrayList<>();
		jsonObj.add(fechas);
		jsonObj.add(dependencias);

		return jsonObj;
	}

	/**
	 * Método para añadir un nuevo pago
	 * 
	 * @param pagos Pagos
	 * @return Pagos
	 */
	// @Transactional(isolation = Isolation.READ_COMMITTED, propagation =
	// Propagation.REQUIRED, rollbackFor = Throwable.class, timeout =
	// PagosServiceImpl.TRANSACTION_TIMEOUT)
	public Procesos anadirPago(Pagos pagos, HttpServletRequest request) {

		// 1- Insertar el nuevo proceso en la tabla 98
		Procesos proceso = new Procesos();
		proceso.setTipoProceso98(ProcesosBatchEnum.ANADIR_PAGO_PRESTADOR.getId());
		proceso.setProceso98(pagos.getAnyoPago74() + "_" + pagos.getMesPago74());
		proceso.setEstado98(EstadosProcesosBatchEnum.PENDIENTE.getId());
		proceso.setUsuAlta98(UtilSeguridad.getNameUsuarioConectado());
		proceso = this.procesosService.add(proceso);

		// 2.- llamada a la O75
		try {
			String result = this.procesosService.lanzarPeticion_anadirPagoPrestador_O75(proceso, pagos, request);
			proceso.setResultadoPeticion(result);
		} catch (Exception e) {
			// Actualizar a error el proceso
			proceso.setEstado98(EstadosProcesosBatchEnum.ERROR.getId());
			proceso.setError98(e.getMessage().substring(0, Math.min(e.getMessage().length(), 799))); // Máximo 800
			this.procesosService.actualizarAError(proceso);
			throw new Ad74aException(e.getMessage());
		}

		return proceso;

	}

	/**
	 * Método para añadir un nuevo pago
	 * 
	 * @param pagos Pagos
	 * @return Pagos
	 */
//	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class, timeout = PagosServiceImpl.TRANSACTION_TIMEOUT)
	public Procesos recalcularPago(Pagos pagos, HttpServletRequest request) {

		// 1- Insertar el nuevo proceso en la tabla 98
		Procesos proceso = new Procesos();
		proceso.setTipoProceso98(ProcesosBatchEnum.RECALCULAR_PAGO_PRESTADOR.getId());
		proceso.setProceso98(pagos.getIdPago74().toString());
		proceso.setEstado98(EstadosProcesosBatchEnum.PENDIENTE.getId());
		proceso.setUsuAlta98(UtilSeguridad.getNameUsuarioConectado());
		proceso = this.procesosService.add(proceso);

		// 2.- llamada a la O75
		try {
			String result = this.procesosService.lanzarPeticion_recalcularPagoPrestador_O75(proceso, pagos, request);
			proceso.setResultadoPeticion(result);
		} catch (Exception e) {
			// actualizar a error el proceso
			proceso.setEstado98(EstadosProcesosBatchEnum.ERROR.getId());
			proceso.setError98(e.getMessage().substring(0, 799)); // Máximo 800 caracteres
			this.procesosService.actualizarAError(proceso);
			throw new Ad74aException(e.getMessage());
		}

		return proceso;
	}

	/**
	 * Devuelve un fichero xlsx que contiene los datos de los pagos.
	 * 
	 * @param pagos    Pagos
	 * @param request  HttpServletRequest
	 * @param response HttpServletResponse
	 */
	@Override
	public void generateExcelReportPagos(Pagos pagos, TableRequestDto tableRequestDto, HttpServletRequest request,
			HttpServletResponse response) {
		// Obtener datos de los beneficiarios
		List<Pagos> listaPagos = this.pagosDao.findAllLike(pagos, tableRequestDto, false);

		// Columnas
		String[] columns = new String[7];
		columns[0] = "anyoPago74";
		columns[1] = "mesPago74";
		columns[2] = "prestadorServicio.idPrestador73";
		columns[3] = "numBenef";
		columns[4] = "numDias";
		columns[5] = "numTarifas";
		columns[6] = "total";

		// Nombre de las columnas
		String[] columnsName = new String[7];
		columnsName[0] = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.anyo");
		columnsName[1] = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.mes");
		columnsName[2] = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.prestador");
		columnsName[3] = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.numBenef");
		columnsName[4] = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.numDias");
		columnsName[5] = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.numTarifas");
		columnsName[6] = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.total");

		// Titulo archivo excel
		String fileName = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.titulo");

		// Titulo hoja excel
		String sheetTitle = UtilLiterales.getLiteralMessage(messageSource, "report.pagos.excel.hoja");

		// Extension archivo
		String extension = ".xlsx";

		// Obtener el formato de fecha especifico del locale
		this.generateExcelReport(listaPagos, columns, columnsName, fileName, sheetTitle, extension, response);
	}

	public static String formatearConPuntos(double valor) {

		// Crear un objeto DecimalFormat para formatear el número
		DecimalFormatSymbols simbolos = new DecimalFormatSymbols();
		simbolos.setGroupingSeparator('.'); // Puntos como separador de miles
		simbolos.setDecimalSeparator(','); // Coma como separador de decimales

		// Definir el patrón de formato para incluir hasta 3 decimales (puedes ajustarlo
		// según tus necesidades)
		DecimalFormat formateador = new DecimalFormat("#,##0.###", simbolos);

		// Formatear el número y añadir " EUR" al final
		return formateador.format(valor) + " €";

	}

	/**
	 * Devuelve un fichero excel que contiene los datos exportados de la tabla.
	 *
	 * @param filteredData List<Pagos>
	 * @param columns      String[]
	 * @param fileName     String
	 * @param sheetTitle   String
	 * @param extension    String
	 * @param response     HttpServletResponse
	 */
	private void generateExcelReport(List<Pagos> listaPagos, String[] columns, String[] columnsName, String fileName,
			String sheetTitle, String extension, HttpServletResponse response) {
		try {
			// Creacion del Excel
			Workbook workbook = null;
			if (extension == ".xlsx") {
				workbook = new XSSFWorkbook();
				response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
			} else {
				workbook = new HSSFWorkbook();
				response.setContentType("application/vnd.ms-excel");
			}

			response.setHeader("Content-Disposition", "attachment; filename=" + fileName + extension + "");

			// Creacion de una hoja y asignacion de su nombre
			Sheet sheet = workbook.createSheet(sheetTitle);

			// Se crea una fuente para estilizar las cabeceras
			Font headerFont = workbook.createFont();
			headerFont.setBold(true);
			headerFont.setFontHeightInPoints((short) 12);

			// Se crea un CellStyle con la fuente
			CellStyle headerCellStyle = workbook.createCellStyle();
			headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
			headerCellStyle.setFont(headerFont);

			// Inicializar contador de filas
			int rowNumber = 0;

			// Se crea la fila para insertar los titulos de las columnas
			Row row = sheet.createRow(rowNumber++);

			// Añadir titulos
			for (int i = 0; i < columnsName.length; i++) {
				Cell cell = row.createCell(i);
				cell.setCellValue(columnsName[i]);
				cell.setCellStyle(headerCellStyle);
			}

			// CreationHelper ayudara a mantener la compatibilidad del DataFormat tanto si
			// se crea un .xls como un .xlsx
			CreationHelper createHelper = workbook.getCreationHelper();

			// Se crea un CellStyle para añadir el formateador de fechas
			CellStyle dateCellStyle = workbook.createCellStyle();
			dateCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("dd/mm/yyyy"));
			DataFormat formatDecimal = workbook.createDataFormat();
			// Se crea un CellStyle para añadir el formateador de nº benef y nº de días
			CellStyle numDiasCellStyle = workbook.createCellStyle();
			numDiasCellStyle.setAlignment(HorizontalAlignment.RIGHT);
			numDiasCellStyle.setDataFormat(formatDecimal.getFormat("#,##0"));
			// Se crea un CellStyle para añadir el formateador de la tarifa y el importe
			CellStyle tarifasCellStyle = workbook.createCellStyle();
			tarifasCellStyle.setAlignment(HorizontalAlignment.RIGHT);
			tarifasCellStyle.setDataFormat(formatDecimal.getFormat("#,##0.00###\" \u20AC\""));
			CellStyle variosCellStyle = workbook.createCellStyle();
			variosCellStyle.setAlignment(HorizontalAlignment.RIGHT);
			CellStyle totalesCellStyle = workbook.createCellStyle();
			totalesCellStyle.setAlignment(HorizontalAlignment.RIGHT);
			totalesCellStyle.setDataFormat(formatDecimal.getFormat("#,##0.00\" \u20AC\""));

			// Añadir datos
			for (Pagos rowPagos : listaPagos) {
				int cellNumber = 0;
				row = sheet.createRow(rowNumber++);

				// Se iteran las columnas y se insertan los datos respetando el orden que tenian
				// las columnas en la tabla
				for (String column : columns) {
					Cell cellPagos = row.createCell(cellNumber++);

					if (null != column) {
						if (column.equals("prestadorServicio.idPrestador73")) {
							cellPagos.setCellValue(rowPagos.getPrestadorServicio().getRazonSocial73());
						} else if (column.equals("numBenef")) {
							cellPagos.setCellStyle(numDiasCellStyle);
							cellPagos.setCellValue(rowPagos.getNumBenef().doubleValue());
						} else if (column.equals("numDias")) {
							cellPagos.setCellStyle(numDiasCellStyle);
							cellPagos.setCellValue(
									(rowPagos.getNumDias() != null) ? rowPagos.getNumDias().doubleValue() : 0);
						} else if (column.equals("numTarifas")) {
							if (rowPagos.getNumTarifas() != null && rowPagos.getNumTarifas() > 1) {
								cellPagos.setCellStyle(variosCellStyle);
								cellPagos.setCellValue("Varios");
							} else {
								cellPagos.setCellStyle(tarifasCellStyle);
								cellPagos.setCellValue(rowPagos.getTarifa().doubleValue());
							}
						} else if (column.equals("total")) {
							cellPagos.setCellStyle(totalesCellStyle);
							cellPagos.setCellValue(rowPagos.getTotal().doubleValue());
						} else if (column.contentEquals("mesPago74")) {
							cellPagos.setCellValue(MesesEnum.getLabelDesc(rowPagos.getMesPago74()));
						} else {
							cellPagos.setCellValue(getCellValue(column, rowPagos));
						}
					}
				}
			}

			// Se adapta el ancho de las columnas al contenido
			for (int i = 0; i < columns.length; i++) {
				sheet.autoSizeColumn(i);
			}

			// Se añade el fichero excel al response
			workbook.write(response.getOutputStream());
			workbook.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
