package de.avatic.lcc.repositories.error;

import de.avatic.lcc.dto.error.CalculationJobDumpDTO;
import de.avatic.lcc.dto.error.CalculationJobDestinationDumpDTO;
import de.avatic.lcc.dto.error.CalculationJobRouteSectionDumpDTO;
import de.avatic.lcc.dto.error.ErrorLogDTO;
import de.avatic.lcc.dto.error.ErrorLogTraceItemDto;
import de.avatic.lcc.dto.calculation.edit.PremiseDetailDTO;
import de.avatic.lcc.repositories.pagination.SearchQueryPagination;
import de.avatic.lcc.repositories.pagination.SearchQueryResult;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.service.transformer.premise.PremiseTransformer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class DumpRepository {

    private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    private final JdbcTemplate jdbcTemplate;
    private final PremiseRepository premiseRepository;
    private final PremiseTransformer premiseTransformer;

    public DumpRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate, JdbcTemplate jdbcTemplate, PremiseRepository premiseRepository, PremiseTransformer premiseTransformer) {
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
        this.jdbcTemplate = jdbcTemplate;
        this.premiseRepository = premiseRepository;
        this.premiseTransformer = premiseTransformer;
    }

    @Transactional(readOnly = true)
    public CalculationJobDumpDTO getDump(Integer id) {
        // First, get the main calculation job
        String calculationJobQuery = """
            SELECT cj.id, cj.premise_id, cj.calculation_date, cj.validity_period_id,
                   cj.property_set_id, cj.job_state, cj.error_id, cj.user_id
            FROM calculation_job cj
            WHERE cj.id = :id
            """;

        MapSqlParameterSource params = new MapSqlParameterSource("id", id);

        CalculationJobDumpDTO dump = namedParameterJdbcTemplate.queryForObject(
                calculationJobQuery,
                params,
                new CalculationMapper());

        // Load premise details
        dump.setPremise(loadPremiseDetails(dump.getPremiseId()));

        // Load destinations
        dump.setDestinations(loadCalculationJobDestinations(id));

        return dump;
    }

    private ErrorLogDTO loadErrorLog(Integer errorId) {
        String errorQuery = """
            SELECT e.id, e.user_id, e.title, e.code, e.message, e.pinia,
                   e.calculation_job_id, e.bulk_operation_id, e.type, e.created_at
            FROM sys_error e
            WHERE e.id = :errorId
            """;

        MapSqlParameterSource params = new MapSqlParameterSource("errorId", errorId);

        try {
            ErrorLogDTO errorLog = namedParameterJdbcTemplate.queryForObject(
                    errorQuery,
                    params,
                    (rs, rowNum) -> {
                        ErrorLogDTO dto = new ErrorLogDTO();
                        dto.setUserId(rs.getObject("user_id", Integer.class));
                        dto.setTitle(rs.getString("title"));
                        dto.setCode(rs.getString("code"));
                        dto.setMessage(rs.getString("message"));
                        dto.setPinia(rs.getString("pinia"));
                        dto.setCalculationJobId(rs.getObject("calculation_job_id", Integer.class));
                        dto.setBulkOperationId(rs.getObject("bulk_operation_id", Integer.class));
                        dto.setType(rs.getString("type"));

                        Timestamp createdAt = rs.getTimestamp("created_at");
                        if (createdAt != null) {
                            dto.setCreatedAt(createdAt.toLocalDateTime());
                        }

                        return dto;
                    });

            // Load trace items
            errorLog.setTrace(loadErrorTraceItems(errorId));

            return errorLog;
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    private List<ErrorLogTraceItemDto> loadErrorTraceItems(Integer errorId) {
        String traceQuery = """
            SELECT line, file, method, fullPath
            FROM sys_error_trace_item
            WHERE error_id = :errorId
            ORDER BY id
            """;

        MapSqlParameterSource params = new MapSqlParameterSource("errorId", errorId);

        return namedParameterJdbcTemplate.query(
                traceQuery,
                params,
                (rs, rowNum) -> {
                    ErrorLogTraceItemDto dto = new ErrorLogTraceItemDto();
                    dto.setLine(rs.getObject("line", Integer.class));
                    dto.setFile(rs.getString("file"));
                    dto.setMethod(rs.getString("method"));
                    dto.setFullPath(rs.getString("fullPath"));
                    return dto;
                });
    }

    private List<CalculationJobDestinationDumpDTO> loadCalculationJobDestinations(Integer calculationJobId) {
        String destinationQuery = """
            SELECT cjd.*,
                   JSON_UNQUOTE(cjd.layer_structure) as layer_structure_json
            FROM calculation_job_destination cjd
            WHERE cjd.calculation_job_id = :calculationJobId
            """;

        MapSqlParameterSource params = new MapSqlParameterSource("calculationJobId", calculationJobId);

        List<CalculationJobDestinationDumpDTO> destinations = namedParameterJdbcTemplate.query(
                destinationQuery,
                params,
                new CalculationJobDestinationRowMapper());

        // Load route sections for each destination
        for (CalculationJobDestinationDumpDTO destination : destinations) {
            destination.setSections(loadRouteSections(destination.getId()));
        }

        return destinations;
    }

    private List<CalculationJobRouteSectionDumpDTO> loadRouteSections(Integer calculationJobDestinationId) {
        String sectionQuery = """
            SELECT id, premise_route_section_id, calculation_job_destination_id,
                   transport_type, is_unmixed_price, is_cbm_price, is_weight_price,
                   is_stacked, is_pre_run, is_main_run, is_post_run,
                   rate, distance, cbm_price, weight_price, annual_cost, transit_time
            FROM calculation_job_route_section
            WHERE calculation_job_destination_id = :destinationId
            ORDER BY id
            """;

        MapSqlParameterSource params = new MapSqlParameterSource("destinationId", calculationJobDestinationId);

        return namedParameterJdbcTemplate.query(
                sectionQuery,
                params,
                (rs, rowNum) -> {
                    CalculationJobRouteSectionDumpDTO dto = new CalculationJobRouteSectionDumpDTO();
                    dto.setId(rs.getInt("id"));
                    dto.setPremiseRouteSectionId(rs.getObject("premise_route_section_id", Integer.class));
                    dto.setCalculationJobDestinationId(rs.getInt("calculation_job_destination_id"));
                    dto.setTransportType(rs.getString("transport_type"));
                    dto.setIsUnmixedPrice(rs.getObject("is_unmixed_price", Boolean.class));
                    dto.setIsCbmPrice(rs.getObject("is_cbm_price", Boolean.class));
                    dto.setIsWeightPrice(rs.getObject("is_weight_price", Boolean.class));
                    dto.setIsStacked(rs.getObject("is_stacked", Boolean.class));
                    dto.setIsPreRun(rs.getObject("is_pre_run", Boolean.class));
                    dto.setIsMainRun(rs.getObject("is_main_run", Boolean.class));
                    dto.setIsPostRun(rs.getObject("is_post_run", Boolean.class));
                    dto.setRate(rs.getBigDecimal("rate"));
                    dto.setDistance(rs.getBigDecimal("distance"));
                    dto.setCbmPrice(rs.getBigDecimal("cbm_price"));
                    dto.setWeightPrice(rs.getBigDecimal("weight_price"));
                    dto.setAnnualCost(rs.getBigDecimal("annual_cost"));
                    dto.setTransitTime(rs.getObject("transit_time", Integer.class));
                    return dto;
                });
    }

    private PremiseDetailDTO loadPremiseDetails(Integer premiseId) {
       return premiseTransformer.toPremiseDetailDTO(premiseRepository.getPremiseById(premiseId).orElseThrow());
    }

    /**
     * Alternative method using JdbcTemplate with lambda if you prefer not to use NamedParameterJdbcTemplate
     */
    public CalculationJobDumpDTO getDumpAlternative(Integer id) {
        String calculationJobQuery = """
            SELECT cj.id, cj.premise_id, cj.calculation_date, cj.validity_period_id,
                   cj.property_set_id, cj.job_state, cj.error_id, cj.user_id
            FROM calculation_job cj
            WHERE cj.id = ?
            """;

        CalculationJobDumpDTO dump = jdbcTemplate.queryForObject(
                calculationJobQuery,
                new CalculationMapper(),
                id  // Parameter passed directly without Object array
        );

        dump.setPremise(loadPremiseDetails(dump.getPremiseId()));
        dump.setDestinations(loadCalculationJobDestinationsAlternative(id));

        return dump;
    }

    private ErrorLogDTO loadErrorLogAlternative(Integer errorId) {
        String errorQuery = """
            SELECT e.id, e.user_id, e.title, e.code, e.message, e.pinia,
                   e.calculation_job_id, e.bulk_operation_id, e.type, e.created_at
            FROM sys_error e
            WHERE e.id = ?
            """;

        return jdbcTemplate.queryForObject(
                errorQuery,
                (rs, rowNum) -> {
                    ErrorLogDTO dto = new ErrorLogDTO();
                    dto.setUserId(rs.getObject("user_id", Integer.class));
                    dto.setTitle(rs.getString("title"));
                    dto.setCode(rs.getString("code"));
                    dto.setMessage(rs.getString("message"));
                    dto.setPinia(rs.getString("pinia"));
                    dto.setCalculationJobId(rs.getObject("calculation_job_id", Integer.class));
                    dto.setBulkOperationId(rs.getObject("bulk_operation_id", Integer.class));
                    dto.setType(rs.getString("type"));

                    Timestamp createdAt = rs.getTimestamp("created_at");
                    if (createdAt != null) {
                        dto.setCreatedAt(createdAt.toLocalDateTime());
                    }

                    return dto;
                },
                errorId
        );
    }

    private List<CalculationJobDestinationDumpDTO> loadCalculationJobDestinationsAlternative(Integer calculationJobId) {
        String destinationQuery = """
            SELECT cjd.*,
                   JSON_UNQUOTE(cjd.layer_structure) as layer_structure_json
            FROM calculation_job_destination cjd
            WHERE cjd.calculation_job_id = ?
            """;

        return jdbcTemplate.query(
                destinationQuery,
                new CalculationJobDestinationRowMapper(),
                calculationJobId
        );
    }

    public SearchQueryResult<CalculationJobDumpDTO> listDumps(SearchQueryPagination searchQueryPagination) {

        String calculationJobQuery = """
            SELECT cj.id, cj.premise_id, cj.calculation_date, cj.validity_period_id,
                   cj.property_set_id, cj.job_state, cj.error_id, cj.user_id
            FROM calculation_job cj
            ORDER BY id DESC LIMIT :limit OFFSET :offset
            """;

        MapSqlParameterSource params = new MapSqlParameterSource();
        params.addValue("offset", searchQueryPagination.getOffset());
        params.addValue("limit", searchQueryPagination.getLimit());

        var dumps = namedParameterJdbcTemplate.query(
                calculationJobQuery,
                params,
                (rs, _) -> {
                    CalculationJobDumpDTO dto = new CalculationJobDumpDTO();
                    dto.setId(rs.getInt("id"));
                    dto.setPremiseId(rs.getInt("premise_id"));

                    Timestamp calculationDate = rs.getTimestamp("calculation_date");
                    if (calculationDate != null) {
                        dto.setCalculationDate(calculationDate.toString());
                    }

                    dto.setValidityPeriodId(rs.getInt("validity_period_id"));
                    dto.setPropertySetId(rs.getInt("property_set_id"));
                    dto.setJobState(rs.getString("job_state"));
                    dto.setUserId(rs.getInt("user_id"));

                    // Check if there's an error_id
                    Integer errorId = rs.getObject("error_id", Integer.class);
                    if (errorId != null) {
                        dto.setErrorLog(loadErrorLog(errorId));
                    }

                    return dto;
                });

        for(var dump : dumps) {
        // Load premise details
            dump.setPremise(loadPremiseDetails(dump.getPremiseId()));

            // Load destinations
            dump.setDestinations(loadCalculationJobDestinations(dump.getId()));

        }

        return new SearchQueryResult<>(dumps, searchQueryPagination.getPage(),countCalculations(), searchQueryPagination.getLimit());
    }

    private class CalculationMapper implements RowMapper<CalculationJobDumpDTO> {

        @Override
        public CalculationJobDumpDTO mapRow(ResultSet rs, int rowNum) throws SQLException {

                CalculationJobDumpDTO dto = new CalculationJobDumpDTO();
                dto.setId(rs.getInt("id"));
                dto.setPremiseId(rs.getInt("premise_id"));

                Timestamp calculationDate = rs.getTimestamp("calculation_date");
                if (calculationDate != null) {
                    dto.setCalculationDate(calculationDate.toString());
                }

                dto.setValidityPeriodId(rs.getInt("validity_period_id"));
                dto.setPropertySetId(rs.getInt("property_set_id"));
                dto.setJobState(rs.getString("job_state"));
                dto.setUserId(rs.getInt("user_id"));

                // Check if there's an error_id
                Integer errorId = rs.getObject("error_id", Integer.class);
                if (errorId != null) {
                    dto.setErrorLog(loadErrorLog(errorId));
                }

                return dto;

        }
    }

    private Integer countCalculations() {
        return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM calculation_job", Integer.class);
    }

    private static class CalculationJobDestinationRowMapper implements RowMapper<CalculationJobDestinationDumpDTO> {
        @Override
        public CalculationJobDestinationDumpDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
            CalculationJobDestinationDumpDTO dto = new CalculationJobDestinationDumpDTO();

            dto.setId(rs.getInt("id"));
            dto.setCalculationJobId(rs.getInt("calculation_job_id"));
            dto.setPremiseDestinationId(rs.getInt("premise_destination_id"));
            dto.setShippingFrequency(rs.getObject("shipping_frequency", Long.class));
            dto.setTotalCost(rs.getBigDecimal("total_cost"));
            dto.setAnnualAmount(rs.getBigDecimal("annual_amount"));

            // Risk fields
            dto.setAnnualRiskCost(rs.getBigDecimal("annual_risk_cost"));
            dto.setAnnualChanceCost(rs.getBigDecimal("annual_chance_cost"));

            // Handling fields
            dto.setIsSmallUnit(rs.getObject("is_small_unit", Boolean.class));
            dto.setAnnualRepackingCost(rs.getBigDecimal("annual_repacking_cost"));
            dto.setAnnualHandlingCost(rs.getBigDecimal("annual_handling_cost"));
            dto.setAnnualDisposalCost(rs.getBigDecimal("annual_disposal_cost"));

            // Inventory fields
            dto.setOperationalStock(rs.getBigDecimal("operational_stock"));
            dto.setSafetyStock(rs.getBigDecimal("safety_stock"));
            dto.setStockedInventory(rs.getBigDecimal("stocked_inventory"));
            dto.setInTransportStock(rs.getBigDecimal("in_transport_stock"));
            dto.setStockBeforePayment(rs.getBigDecimal("stock_before_payment"));
            dto.setAnnualCapitalCost(rs.getBigDecimal("annual_capital_cost"));
            dto.setAnnualStorageCost(rs.getBigDecimal("annual_storage_cost"));

            // Custom fields
            dto.setCustomValue(rs.getBigDecimal("custom_value"));
            dto.setCustomDuties(rs.getBigDecimal("custom_duties"));
            dto.setTariffRate(rs.getBigDecimal("tariff_rate"));
            dto.setAnnualCustomCost(rs.getBigDecimal("annual_custom_cost"));

            // Air freight fields
            dto.setAirFreightShareMax(rs.getBigDecimal("air_freight_share_max"));
            dto.setAirFreightShare(rs.getBigDecimal("air_freight_share"));
            dto.setAirFreightVolumetricWeight(rs.getBigDecimal("air_freight_volumetric_weight"));
            dto.setAirFreightWeight(rs.getBigDecimal("air_freight_weight"));
            dto.setAnnualAirFreightCost(rs.getBigDecimal("annual_air_freight_cost"));

            // Transportation fields
            dto.setIsD2d(rs.getObject("is_d2d", Boolean.class));
            dto.setRateD2d(rs.getBigDecimal("rate_d2d"));
            dto.setContainerType(rs.getString("container_type"));
            dto.setHuCount(rs.getObject("hu_count", Long.class));

            // Handle JSON layer_structure
            String layerStructureJson = rs.getString("layer_structure_json");
            if (layerStructureJson != null) {
                // You might want to use Jackson or another JSON library here
                // For now, storing as a simple map
                Map<String, Object> layerStructure = new HashMap<>();
                layerStructure.put("raw", layerStructureJson);
                dto.setLayerStructure(layerStructure);
            }

            dto.setLayerCount(rs.getObject("layer_count", Long.class));
            dto.setTransportWeightExceeded(rs.getObject("transport_weight_exceeded", Boolean.class));
            dto.setAnnualTransportationCost(rs.getBigDecimal("annual_transportation_cost"));
            dto.setContainerUtilization(rs.getBigDecimal("container_utilization"));
            dto.setTransitTimeInDays(rs.getObject("transit_time_in_days", Long.class));
            dto.setSafetyStockInDays(rs.getObject("safety_stock_in_days", Long.class));

            // Material cost fields
            dto.setMaterialCost(rs.getBigDecimal("material_cost"));
            dto.setFcaCost(rs.getBigDecimal("fca_cost"));

            return dto;
        }
    }
}