package com.trackingpremium.report.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;

import com.trackingpremium.report.dto.CompanyRequest;
import com.trackingpremium.report.dto.ReportDetail;
import com.trackingpremium.report.dto.ReportResponse;
import com.trackingpremium.report.model.DatabaseConnection;
import jakarta.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.engine.util.JRSaver;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;
import net.sf.jasperreports.export.SimpleWriterExporterOutput;
import org.yaml.snakeyaml.Yaml;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Service
public class ReportService {

	private final DatabaseConnection databaseConnection;
	private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
	private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/tp_reports";
	private static final long CLEANUP_INTERVAL = 15; // minutes
	private static final long FILE_MAX_AGE = 60; // minutes

	private static final Logger logger = LoggerFactory.getLogger(ReportService.class);// custom list
	private Path rootPath;// custom list

	@Value("${jasper.reports.path}")
	private String jasperReportsPath;

	@Value("${servicio.base-de-datos}")
	private String nombreBaseDeDatos;

	@Autowired
	public ReportService(DatabaseConnection databaseConnection) {
		this.databaseConnection = databaseConnection;
	}

	@PostConstruct
	public void init() {
		// Create temp directory if it doesn't exist
		new File(TEMP_DIR).mkdirs();

		// Schedule cleanup task
		scheduler.scheduleAtFixedRate(this::cleanupOldFiles, CLEANUP_INTERVAL, CLEANUP_INTERVAL, TimeUnit.MINUTES);

		// custom list
		// Inicializamos la ruta raíz de los reportes
		this.rootPath = Paths.get(jasperReportsPath).normalize();

		// Verificamos si la ruta existe al iniciar
		if (!Files.exists(rootPath)) {
			logger.warn("⚠️ La ruta de reportes no existe en el arranque: {}", jasperReportsPath);
		}
	}

	// custom list
	/**
	 * Busca los reportes personalizados para una empresa específica recorriendo las
	 * subcarpetas de tipos de reporte.
	 * con esta lógica los muestra asi:
	 *  {
        "label": "PDFdefaultBase",
        "value": "PDFdefaultBase.jasper",
        "type": "Warehouse"
    }
	 */
	public List<ReportResponse> obtenerReportesPersonalizadosPorTipo(CompanyRequest request) {
		//en reserva 
		// 1. Construimos la ruta directa: root/tipo/empresa
		// Ej: C:/tp_reports/reports/Consolidated/onthewaylogistic
		Path carpetaEmpresa = rootPath.resolve(request.getReportType()).resolve(request.getCompanyName());

		if (Files.exists(carpetaEmpresa) && Files.isDirectory(carpetaEmpresa)) {
			try (Stream<Path> archivos = Files.list(carpetaEmpresa)) {
				return archivos.filter(f -> f.toString().endsWith(".jasper"))
						// excluir encabezados
						.filter(f -> !f.getFileName().toString().toLowerCase().contains("encabezado"))
						// excluir term && cond
						.filter(f -> !f.getFileName().toString().toLowerCase().contains("termcond"))
						.map(f -> {
							String archivo = f.getFileName().toString();
							String etiqueta = archivo.replace(".jasper", "");
							return new ReportResponse(etiqueta, archivo, request.getReportType());
						}).collect(Collectors.toList());
			} catch (IOException e) {
				logger.error("Error al leer archivos en {}", carpetaEmpresa, e);
			}
		}

		return List.of(); // Si no existe la combinación tipo/empresa, devolvemos lista vacía
	}
	
	public Map<String, List<ReportDetail>> obtenerReportesAgrupados(CompanyRequest request) {
		//en uso
	    Map<String, List<ReportDetail>> mapaFinal = new HashMap<>();
	    
	    // 1. Listamos las carpetas de TIPOS (Warehouse, Consolidated, etc.) en el root
	    try (Stream<Path> carpetasTipos = Files.list(rootPath)) {
	        carpetasTipos.filter(Files::isDirectory).forEach(tipoFolder -> {
	            String nombreTipo = tipoFolder.getFileName().toString();
	            
	            // 2. Buscamos si dentro de ese tipo existe la carpeta de la empresa
	            Path rutaEmpresa = tipoFolder.resolve(request.getCompanyName());
	            
	            if (Files.exists(rutaEmpresa) && Files.isDirectory(rutaEmpresa)) {
	                try (Stream<Path> archivos = Files.list(rutaEmpresa)) {
	                    List<ReportDetail> reportes = archivos
	                        .filter(f -> f.toString().endsWith(".jasper"))
	                        .filter(f -> !f.getFileName().toString().toLowerCase().contains("encabezado"))
	                        .filter(f -> !f.getFileName().toString().toLowerCase().contains("termcond"))
	                        .map(f -> new ReportDetail(f.getFileName().toString().replace(".jasper", ""), f.getFileName().toString()))
	                        .collect(Collectors.toList());

	                    if (!reportes.isEmpty()) {
	                        mapaFinal.put(nombreTipo, reportes); // "Warehouse": [lista de reportes]
	                    }
	                } catch (IOException e) { /* log */ }
	            }
	        });
	    } catch (IOException e) {
	        logger.error("Error leyendo rootPath", e);
	    }
	    return mapaFinal;
	}
	// end custom list

	@PreDestroy
	public void cleanup() {
		scheduler.shutdown();
		try {
			if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
				scheduler.shutdownNow();
			}
		} catch (InterruptedException e) {
			scheduler.shutdownNow();
		}
	}

	private void cleanupOldFiles() {
		try {
			File tempDir = new File(TEMP_DIR);
			if (!tempDir.exists())
				return;

			File[] files = tempDir.listFiles();
			if (files == null)
				return;

			long currentTime = System.currentTimeMillis();
			for (File file : files) {
				long fileAge = currentTime - file.lastModified();
				if (fileAge > TimeUnit.MINUTES.toMillis(FILE_MAX_AGE)) {
					file.delete();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private String generateFileName(String name, String format) {
		StringBuilder fileName = new StringBuilder();

		// Add report name
		fileName.append(name);

		// Add timestamp
		fileName.append("_").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")));

		// Add format extension
		fileName.append(".").append(format.toLowerCase());

		return fileName.toString();
	}

	@SuppressWarnings("deprecation")
	private JasperPrint generateJasperPrint(String company, String type, String name, Map<String, Object> params)
			throws Exception {
		// String jasperFileName = name + ".jrxml";
		String jasperFileName = name.trim() + ".jasper";
		String rootFolder = jasperReportsPath + type + "/";

		// ... Locate the custom reports folder and report
		String subRootFolder = rootFolder + company + "/";
		File subDirFile = new File(subRootFolder + jasperFileName);
		if (subDirFile.exists() && subDirFile.canRead()) {
			rootFolder = subRootFolder;
		}

		String jrxmlPath = rootFolder + jasperFileName;

		Connection conn = null;
		try {
			// 1. Get database connection
			conn = databaseConnection.getConnection();

			// 2. Load the .jrxml file
			JasperReport jasperReport = null;
			InputStream jasperStream = null;

			try {
				jasperStream = new FileInputStream(new File(jrxmlPath));
				jasperReport = (JasperReport) JRLoader.loadObject(jasperStream);
				jasperStream.close();
			} catch (FileNotFoundException e) {

				throw new Exception("Could not find Jasper file: " + jrxmlPath, e);
			}

			// 3. Fill the report with database data and parameters
			Map<String, Object> parameters = (params != null) ? params : new HashMap<>();

			// only for dashboard
			if (params != null && params.containsKey("companyId")) {
				parameters.put("companyId", params.get("companyId"));
			}
			if (params != null && params.containsKey("start_date")) {
				parameters.put("start_date", params.get("start_date"));
			}
			if (params != null && params.containsKey("end_date")) {
				parameters.put("end_date", params.get("end_date"));
			}

			// for everyone else
			if (params != null && params.containsKey("documentId")) {
				parameters.put("documentId", params.get("documentId"));
			}

			if (params != null && params.containsKey("language")) {
				// label language
				parameters.put("language", params.get("language"));
				// local format for numbers
				Object languageObj = params.get("language");
				if (languageObj != null && languageObj instanceof String) {
					String languageCode = (String) languageObj;
					if ("en".equals(languageCode)) {
						Locale locale = new Locale("en", "US");
						parameters.put(JRParameter.REPORT_LOCALE, locale);
					} else {
						Locale locale = new Locale("es", "VE");
						parameters.put(JRParameter.REPORT_LOCALE, locale);
					}
				} else {
					// Locale por defecto.
					Locale defaultLocale = new Locale("es", "VE");
					parameters.put(JRParameter.REPORT_LOCALE, defaultLocale);
				}
			} else {
				// If parameter map key 'language' it does not exist, then local by default.
				Locale defaultLocale = new Locale("es", "VE");
				parameters.put(JRParameter.REPORT_LOCALE, defaultLocale);
			}

			// // Read the reports-config.yaml file and found the subreports
			ClassPathResource resourcey = new ClassPathResource("reports/config/reports-config.yaml");
			InputStream inputStream = resourcey.getInputStream();

			Yaml yaml = new Yaml();
			Map<String, Object> config = yaml.load(inputStream);

			Map<String, Object> reportConfig = (Map<String, Object>) config.get(type.trim());
			if (reportConfig == null) {
				throw new Exception("Could not find report config for type: " + type);
			}

			// *** LÍNEA DE DIAGNÓSTICO TEMPORAL ***
			System.out.println("Keys in reportConfig: " + reportConfig.keySet());

			Map<String, Object> report = (Map<String, Object>) reportConfig.get(name.trim());

			if (report == null) {
				// Esto es un 'safe exit' para evitar el NPE y dar un diagnóstico claro.
				throw new Exception(
						"ERROR: The report configuration name '" + name.trim() + "' was not found under type '" + type
								+ "'. Check for case sensitivity or invisible characters in the YAML/request.");
			}

			List<Map<String, Object>> subreports = (List<Map<String, Object>>) report.get("subreports");

			for (Map<String, Object> subreport : subreports) {
				String subreportName = (String) subreport.get("name");
				// ... The subreport's path must also be evaluated if a custom report exists.
				String subreportJrxmlPath = null;
				if (subDirFile.exists() && subDirFile.canRead()) {
					// subreportJrxmlPath = type + "/" + company + "/" + subreportName + ".jrxml";
					subreportJrxmlPath = type + "/" + company + "/" + subreportName + ".jasper";
				} else {
					// subreportJrxmlPath = type + "/" + subreportName + ".jrxml";
					subreportJrxmlPath = type + "/" + subreportName + ".jasper";
				}
				String fullSubreportPath = jasperReportsPath + subreportJrxmlPath;
				// System.out.println("full ruta " + fullSubreportPath);

				InputStream subreportStream;
				try {
					subreportStream = new FileInputStream(new File(fullSubreportPath));
				} catch (FileNotFoundException e) {
					throw new Exception("Could not find subreport file: " + fullSubreportPath, e);
				}
				JasperReport compiledSubreport = (JasperReport) JRLoader.loadObject(subreportStream);
				// JasperReport compiledSubreport =
				// JasperCompileManager.compileReport(subreportStream);
				subreportStream.close();
				JRSaver.saveObject(compiledSubreport, subreportName + ".jasper");
				parameters.put("SUBREPORT_DIR_" + subreportName, subreportName + ".jasper");
			}

			// parameters.put("logo_url", defaultImageUrl);

			return JasperFillManager.fillReport(jasperReport, parameters, conn);

		} finally

		{
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public String generateReport(String company, String type, String name, Map<String, Object> params, String format)
			throws Exception {
		try {
			JasperPrint jasperPrint = generateJasperPrint(company, type, name, params);
			String fileName = generateFileName(name, format);

			if (format.equalsIgnoreCase("html")) {
				// Create temp directory if it doesn't exist
				File tempDir = new File(TEMP_DIR);
				if (!tempDir.exists()) {
					tempDir.mkdirs();
				}

				String htmlFilePath = tempDir.getAbsolutePath() + File.separator + fileName;
				JasperExportManager.exportReportToHtmlFile(jasperPrint, htmlFilePath);
				return "/reports/html/" + fileName;
			} else {
				throw new Exception("Unsupported format: " + format);
			}
		} catch (JRException e) {
			throw new Exception("Error generating report: " + e.getMessage(), e);
		}
	}

	public void generateReport(HttpServletResponse response, String company, String type, String name,
			Map<String, Object> params, String format) throws Exception {
		try {
			JasperPrint jasperPrint = generateJasperPrint(company, type, name, params);
			OutputStream out = response.getOutputStream();
			String fileName = generateFileName(name, format);

			switch (format.toLowerCase()) {
			case "pdf":
				response.setContentType("application/pdf");
				response.setHeader("Content-Disposition", "inline; filename=" + fileName);
				JasperExportManager.exportReportToPdfStream(jasperPrint, out);
				break;

			case "xlsx":
				response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
				response.setHeader("Content-Disposition", "inline; filename=" + fileName);
				JRXlsxExporter exporter = new JRXlsxExporter();
				exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
				exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(out));
				exporter.exportReport();
				break;

			case "csv":
				response.setContentType("text/csv");
				response.setHeader("Content-Disposition", "inline; filename=" + fileName);
				JRCsvExporter csvExporter = new JRCsvExporter();
				csvExporter.setExporterInput(new SimpleExporterInput(jasperPrint));
				csvExporter.setExporterOutput(new SimpleWriterExporterOutput(out));
				csvExporter.exportReport();
				break;

			default:
				throw new Exception("Unsupported format: " + format);
			}

			out.close();
		} catch (JRException e) {
			throw new Exception("Error generating report: " + e.getMessage(), e);
		}
	}

}