package jumapacelaya.gob.mx.predios.servicio;

import io.micronaut.transaction.annotation.ReadOnly;
import io.micronaut.transaction.annotation.Transactional;
import jakarta.inject.Singleton;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;

import jumapacelaya.gob.mx.predios.dto.PredioDTO;

@Singleton
public class PredioServicio {

    private final DataSource dataSource;

    public PredioServicio(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @ReadOnly
    public PredioDTO obtenerPredio(Long predioId) {
        String query = "SELECT predioid, clienteid, contrato, c.nombre nomcliente, " +
                "fn_getdomicilio(p.direcid) direccion, fn_get_direccion(p.direcid) direccionmostrar, " +
                "fn_direccionshort(p.direcid) direccioncorta, fn_gettelefono(clienteid) telefono, " +
                "p.coordx, p.coordy, p.zonafactid, p.serialmed, " +
                "marcamedid||'-'||ma.descripcion marcamed, " +
                "(decode(mo.descripcion,null,'Desconocido',modelomedid||'-'||mo.descripcion)) modelomed, " +
                "edopredioid, e.nombre edopredio, usoid, u.nombre uso, actividadid, a.nombre actividad " +
                "FROM predios p " +
                "LEFT JOIN clientes c USING (clienteid) " +
                "LEFT JOIN estadospredio e USING(edopredioid) " +
                "LEFT JOIN usos u USING (usoid) " +
                "LEFT JOIN actividades a USING (usoid, actividadid) " +
                "LEFT JOIN marcasmedidor ma USING(marcamedid) " +
                "LEFT JOIN modelosmedidor mo USING(marcamedid, modelomedid) " +
                "WHERE predioid = ?";

        try (Connection connection = dataSource.getConnection();
             PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return new PredioDTO(
                            rs.getLong("predioid"),
                            rs.getLong("clienteid"),
                            rs.getString("contrato"),
                            rs.getString("nomcliente"),
                            rs.getString("direccion"),
                            rs.getString("direccionmostrar"),
                            rs.getString("direccioncorta"),
                            rs.getString("telefono"),
                            rs.getDouble("coordx"),
                            rs.getDouble("coordy"),
                            rs.getString("zonafactid"),
                            rs.getString("serialmed"),
                            rs.getString("marcamed"),
                            rs.getString("modelomed"),
                            rs.getString("edopredioid"),
                            rs.getString("edopredio"),
                            rs.getString("usoid"),
                            rs.getString("uso"),
                            rs.getInt("actividadid"),
                            rs.getString("actividad")
                    );
                } else {
                    return null;
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al consultar predio: " + e.getMessage(), e);
        }
    }

    @ReadOnly
    public Optional<PredioDTO> obtenerPredioVar(Long predioId) {
        String query = """
            SELECT predioid, clienteid, contrato, c.nombre nomcliente,
            fn_getdomicilio(p.direcid) direccion, fn_get_direccion(p.direcid) direccionmostrar, fn_direccionshort(p.direcid) direccioncorta,
            fn_gettelefono(clienteid) telefono,
            p.coordx, p.coordy, p.zonafactid, p.serialmed,
            marcamedid || '-' || ma.descripcion marcamed,
            DECODE(mo.descripcion, NULL, 'Desconocido', modelomedid || '-' || mo.descripcion) modelomed,
            edopredioid, e.nombre edopredio, usoid, u.nombre uso,
            actividadid, a.nombre actividad,
            (SELECT SUM(total) FROM TABLE(cobranza.pk_carven.fn_getadeudocortes(predioid, TO_CHAR(SYSDATE, 'dd/mm/yyyy')))) adeudocortes
            FROM predios p
            LEFT JOIN clientes c USING (clienteid)
            LEFT JOIN estadospredio e USING (edopredioid)
            LEFT JOIN usos u USING (usoid)
            LEFT JOIN actividades a USING (usoid, actividadid)
            LEFT JOIN marcasmedidor ma USING (marcamedid)
            LEFT JOIN modelosmedidor mo USING (marcamedid, modelomedid)
            WHERE predioid = ?
            """;

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    PredioDTO dto = new PredioDTO(
                        rs.getLong("predioid"),
                        rs.getLong("clienteid"),
                        rs.getString("contrato"),
                        rs.getString("nomcliente"),
                        rs.getString("direccion"),
                        rs.getString("direccionmostrar"),
                        rs.getString("direccioncorta"),
                        rs.getString("telefono"),
                        rs.getDouble("coordx"),
                        rs.getDouble("coordy"),
                        rs.getString("zonafactid"),
                        rs.getString("serialmed"),
                        rs.getString("marcamed"),
                        rs.getString("modelomed"),
                        rs.getString("edopredioid"),
                        rs.getString("edopredio"),
                        rs.getString("usoid"),
                        rs.getString("uso"),
                        rs.getInt("actividadid"),
                        rs.getString("actividad"),
                        rs.getDouble("adeudocortes") // asegúrate de haberlo agregado al DTO
                    );
                    return Optional.of(dto);
                } else {
                    return Optional.empty();
                }
            }

        } catch (SQLException e) {
            throw new RuntimeException("Error al consultar predio var: " + e.getMessage(), e);
        }
    }

    @ReadOnly
    public Optional<PredioDTO> obtenerPredioATM(String valor) {
        if ("7777777".equals(valor)) {
            return Optional.of(new PredioDTO(
                7777777L, 7777777L, "7777777", "Cliente de Prueba ATMs",
                "Dirección ficticia", "Dirección ficticia larga", "Dir. Falsa",
                null, 0.0, 0.0, "G7", "13166429", "NO-AUTOMATICO", "Desconocido",
                "NO", "NORMAL", "T", "TESTING", 7, "TESTING", 0.0
            ));
        }

        String query;
        boolean isLong = valor.length() == 30;
        boolean isContract = valor.length() == 8;
        boolean isPredioId = valor.length() <= 6;

        if (isLong) {
            query = """
                SELECT predioid, contrato, clienteid, c.nombre nomcliente,
                fn_getdomicilio(p.direcid) direccion, fn_get_direccion(p.direcid) direccionmostrar,
                fn_direccionshort(p.direcid) direccioncorta, p.coordx, p.coordy, p.zonafactid,
                p.serialmed, edopredioid, e.nombre edopredio, usoid, u.nombre uso,
                actividadid, a.nombre actividad
                FROM predios p
                LEFT JOIN clientes c USING (clienteid)
                LEFT JOIN estadospredio e USING (edopredioid)
                LEFT JOIN usos u USING (usoid)
                LEFT JOIN actividades a USING (usoid, actividadid)
                WHERE predioid = TO_NUMBER(SUBSTR(?, 24, 6))
            """;
        } else if (isContract) {
            query = """
                SELECT predioid, contrato, clienteid, c.nombre nomcliente,
                fn_getdomicilio(p.direcid) direccion, fn_get_direccion(p.direcid) direccionmostrar,
                fn_direccionshort(p.direcid) direccioncorta, p.coordx, p.coordy, p.zonafactid,
                p.serialmed, edopredioid, e.nombre edopredio, usoid, u.nombre uso,
                actividadid, a.nombre actividad
                FROM predios p
                LEFT JOIN clientes c USING (clienteid)
                LEFT JOIN estadospredio e USING (edopredioid)
                LEFT JOIN usos u USING (usoid)
                LEFT JOIN actividades a USING (usoid, actividadid)
                WHERE REGEXP_LIKE(contrato, ?)
            """;
        } else if (isPredioId) {
            query = """
                SELECT predioid, contrato, clienteid, c.nombre nomcliente,
                fn_getdomicilio(p.direcid) direccion, fn_get_direccion(p.direcid) direccionmostrar,
                fn_direccionshort(p.direcid) direccioncorta, p.coordx, p.coordy, p.zonafactid,
                p.serialmed, edopredioid, e.nombre edopredio, usoid, u.nombre uso,
                actividadid, a.nombre actividad
                FROM predios p
                LEFT JOIN clientes c USING (clienteid)
                LEFT JOIN estadospredio e USING (edopredioid)
                LEFT JOIN usos u USING (usoid)
                LEFT JOIN actividades a USING (usoid, actividadid)
                WHERE predioid = TO_NUMBER(?)
            """;
        } else {
            return Optional.empty();
        }

        try (Connection conn = dataSource.getConnection();
            PreparedStatement stmt = conn.prepareStatement(query)) {

            stmt.setString(1, valor);
            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.of(new PredioDTO(
                        rs.getLong("predioid"),
                        rs.getLong("clienteid"),
                        rs.getString("contrato"),
                        rs.getString("nomcliente"),
                        rs.getString("direccion"),
                        rs.getString("direccionmostrar"),
                        rs.getString("direccioncorta"),
                        null,
                        rs.getDouble("coordx"),
                        rs.getDouble("coordy"),
                        rs.getString("zonafactid"),
                        rs.getString("serialmed"),
                        null,
                        null,
                        rs.getString("edopredioid"),
                        rs.getString("edopredio"),
                        rs.getString("usoid"),
                        rs.getString("uso"),
                        rs.getInt("actividadid"),
                        rs.getString("actividad"),
                        null,
                        null
                    ));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al consultar predio ATM: " + e.getMessage(), e);
        }

        return Optional.empty();
    }

    @ReadOnly
    public Optional<PredioDTO> obtenerPredioAPC(String valor) {
        String query = """
            SELECT predioid, contrato, clienteid, c.nombre nomcliente,
                   fn_getdomicilio(p.direcid) direccion, fn_get_direccion(p.direcid) direccionmostrar,
                   fn_direccionshort(p.direcid) direccioncorta,
                   p.coordx, p.coordy, p.zonafactid, p.serialmed,
                   edopredioid, e.nombre edopredio, usoid, u.nombre uso,
                   actividadid, a.nombre actividad
            FROM predios p
            LEFT JOIN clientes c USING (clienteid)
            LEFT JOIN estadospredio e USING(edopredioid)
            LEFT JOIN usos u USING (usoid)
            LEFT JOIN actividades a USING (usoid, actividadid)
            WHERE (
                (LENGTH(?) = 30 AND predioid = SUBSTR(?, 24, 6)) OR
                (LENGTH(?) = 8 AND REGEXP_LIKE(contrato, ?)) OR
                (LENGTH(?) <= 6 AND predioid = TO_NUMBER(?))
            )
            """;

        try (Connection connection = dataSource.getConnection();
             PreparedStatement stmt = connection.prepareStatement(query)) {

            for (int i = 1; i <= 6; i++) {
                stmt.setString(i, valor);
            }

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.of(new PredioDTO(
                            rs.getLong("predioid"),
                            rs.getLong("clienteid"),
                            rs.getString("contrato"),
                            rs.getString("nomcliente"),
                            rs.getString("direccion"),
                            rs.getString("direccionmostrar"),
                            rs.getString("direccioncorta"),
                            null,
                            rs.getDouble("coordx"),
                            rs.getDouble("coordy"),
                            rs.getString("zonafactid"),
                            rs.getString("serialmed"),
                            null,
                            null,
                            rs.getString("edopredioid"),
                            rs.getString("edopredio"),
                            rs.getString("usoid"),
                            rs.getString("uso"),
                            rs.getInt("actividadid"),
                            rs.getString("actividad")
                    ));
                } else {
                    return Optional.empty();
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al consultar predio APC: " + e.getMessage(), e);
        }
    }

    @ReadOnly
    public Optional<PredioDTO> obtenerPredioAppOTs(Long predioId) {
        String query = """
            SELECT predioid, contrato, clienteid, c.nombre nomcliente,
            fn_getdomicilio(p.direcid) direccion, fn_get_direccion(p.direcid) direccionmostrar,
            fn_direccionshort(p.direcid) direccioncorta,
            p.coordx, p.coordy, p.zonafactid, p.serialmed,
            edopredioid, e.nombre edopredio, usoid, u.nombre uso,
            actividadid, a.nombre actividad, fn_get_tarifa(p.predioid) tarifa
            FROM predios p
            LEFT JOIN clientes c USING (clienteid)
            LEFT JOIN estadospredio e USING (edopredioid)
            LEFT JOIN usos u USING (usoid)
            LEFT JOIN actividades a USING (usoid, actividadid)
            WHERE predioid = ?
            """;

        try (Connection connection = dataSource.getConnection();
             PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.of(new PredioDTO(
                            rs.getLong("predioid"),
                            rs.getLong("clienteid"),
                            rs.getString("contrato"),
                            rs.getString("nomcliente"),
                            rs.getString("direccion"),
                            rs.getString("direccionmostrar"),
                            rs.getString("direccioncorta"),
                            null,
                            rs.getDouble("coordx"),
                            rs.getDouble("coordy"),
                            rs.getString("zonafactid"),
                            rs.getString("serialmed"),
                            null,
                            null,
                            rs.getString("edopredioid"),
                            rs.getString("edopredio"),
                            rs.getString("usoid"),
                            rs.getString("uso"),
                            rs.getInt("actividadid"),
                            rs.getString("actividad"),
                            null,
                            rs.getString("tarifa")
                        )
                    );
                } else {
                    return Optional.empty();
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al consultar predio AppOTs: " + e.getMessage(), e);
        }
    }

    @ReadOnly
    public Optional<PredioDTO> obtenerDireccionPredio(Long predioId) {
        String query = """
            SELECT predioid, contrato, clienteid, c.nombre nomcliente,
                fn_getdomicilio(p.direcid) direccion,
                fn_get_direccion(p.direcid) direccionmostrar,
                fn_direccionshort(p.direcid) direccioncorta
            FROM predios p
            LEFT JOIN clientes c USING (clienteid)
            WHERE predioid = ?
        """;

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.of(new PredioDTO(
                            rs.getLong("predioid"),
                            rs.getLong("clienteid"),
                            rs.getString("contrato"),
                            rs.getString("nomcliente"),
                            rs.getString("direccion"),
                            rs.getString("direccionmostrar"),
                            rs.getString("direccioncorta"),
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null,
                            null
                    ));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener dirección del predio: " + e.getMessage(), e);
        }
        return Optional.empty();
    }

    @ReadOnly
    public String obtenerDireccionCooper(Long predioId) {
        String query = "SELECT fn_direccionshort(p.direcid) AS direccion FROM predios p WHERE predioid = ?";

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    String direccion = rs.getString("direccion");
                    if (direccion != null && !direccion.isBlank()) {
                        return "TU DIRECCIÓN ES: " + direccion;
                    }
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener la dirección corta del predio: " + e.getMessage(), e);
        }

        return "El número de predio: " + predioId + " no existe. Por favor verificalo en tu recibo.";
    }

    @ReadOnly
    public Optional<String> obtenerDireccionCoopers(Long predioId) {
        String query = "SELECT fn_direccionshort(p.direcid) AS direccion FROM predios p WHERE predioid = ?";

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.ofNullable(rs.getString("direccion"));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener la dirección corta cruda: " + e.getMessage(), e);
        }

        return Optional.empty();
    }

    @ReadOnly
    public Optional<Double> obtenerCoordX(Long predioId) {
        String query = "SELECT coordx FROM predios WHERE predioid = ?";

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.ofNullable(rs.getDouble("coordx"));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener coordx: " + e.getMessage(), e);
        }

        return Optional.empty();
    }

    @ReadOnly
    public Optional<Double> obtenerCoordY(Long predioId) {
        String query = "SELECT coordy FROM predios WHERE predioid = ?";

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.ofNullable(rs.getDouble("coordy"));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener coordy: " + e.getMessage(), e);
        }

        return Optional.empty();
    }
    
    @ReadOnly
    public Optional<Long> obtenerClienteId(Long predioId) {
        String query = "SELECT clienteid FROM predios WHERE predioid = ?";

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.of(rs.getLong("clienteid"));
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener clienteid: " + e.getMessage(), e);
        }

        return Optional.empty();
    }

    @ReadOnly
    public Optional<Long> obtenerOT(Long predioId, String motivoId) {
        String query = """
            SELECT MAX(otid) otid 
            FROM ordentrabajoenc 
            WHERE predioid = ? 
            AND motivoid = ? 
            AND fechaprecierre IS NULL 
            AND fechaanulacion IS NULL
        """;

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);
            stmt.setString(2, motivoId);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    long otid = rs.getLong("otid");
                    if (!rs.wasNull()) {
                        return Optional.of(otid);
                    }
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener OT: " + e.getMessage(), e);
        }

        return Optional.empty();
    }

    @ReadOnly
    public Optional<String> obtenerSituacionCorte(Long predioId, String tipo) {
        String query = "SELECT fn_getsituacioncorte(?, ?) AS situacion FROM dual";

        try (Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement(query)) {

            stmt.setLong(1, predioId);
            stmt.setString(2, tipo);

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    String situacion = rs.getString("situacion");
                    if (situacion != null && !situacion.trim().isEmpty() &&
                        !situacion.equalsIgnoreCase("Sin Corte") &&
                        !situacion.equalsIgnoreCase("Reconexion Solicitada")) {
                        return Optional.of(situacion);
                    }
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException("Error al consultar situación de corte: " + e.getMessage(), e);
        }

        return Optional.empty();
    }

    @Transactional
    public PredioDTO obtenerPredioAppOts(String vPredContrRefer) {
        String query = """
            SELECT predioid, contrato, clienteid, c.nombre AS nomcliente,
                fn_getdomicilio(p.direcid) AS direccion,
                fn_get_direccion(p.direcid) AS direccionmostrar,
                fn_direccionshort(p.direcid) AS direccioncorta,
                p.coordx, p.coordy, p.zonafactid, p.serialmed,
                edopredioid, e.nombre AS edopredio,
                usoid, u.nombre AS uso,
                actividadid, a.nombre AS actividad,
                fn_get_tarifa(p.predioid) AS tarifa
            FROM predios p
            LEFT JOIN clientes c USING (clienteid)
            LEFT JOIN estadospredio e USING (edopredioid)
            LEFT JOIN usos u USING (usoid)
            LEFT JOIN actividades a USING (usoid, actividadid)
            WHERE (
                CASE
                    WHEN LENGTH(?) = 30 AND predioid = SUBSTR(?, 24, 6) THEN 1
                    WHEN LENGTH(?) = 8 AND REGEXP_LIKE(contrato, ?) THEN 1
                    WHEN LENGTH(?) <= 6 AND predioid = TO_NUMBER(?) THEN 1
                    ELSE 0
                END
            ) = 1
            """;

        try (Connection conn = dataSource.getConnection();
            PreparedStatement ps = conn.prepareStatement(query)) {

            ps.setString(1, vPredContrRefer);
            ps.setString(2, vPredContrRefer);
            ps.setString(3, vPredContrRefer);
            ps.setString(4, vPredContrRefer);
            ps.setString(5, vPredContrRefer);
            ps.setString(6, vPredContrRefer);

            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                PredioDTO dto = new PredioDTO(
                    rs.getLong("predioid"),
                    rs.getLong("clienteid"),
                    rs.getString("contrato"),
                    rs.getString("nomcliente"),
                    rs.getString("direccion"),
                    rs.getString("direccionmostrar"),
                    rs.getString("direccioncorta"),
                    null,
                    rs.getBigDecimal("coordx") != null ? rs.getBigDecimal("coordx").doubleValue() : null,
                    rs.getBigDecimal("coordy") != null ? rs.getBigDecimal("coordy").doubleValue() : null,
                    rs.getString("zonafactid"),
                    rs.getString("serialmed"),
                    null,
                    null,
                    rs.getString("edopredioid"),
                    rs.getString("edopredio"),
                    rs.getString("usoid"),
                    rs.getString("uso"),
                    rs.getInt("actividadid"),
                    rs.getString("actividad"),
                    rs.getString("tarifa")
                );

                return dto;
            } else {
                return null;
            }

        } catch (SQLException e) {
            throw new RuntimeException("Error al obtener predio para AppOTs: " + e.getMessage(), e);
        }
    }


}
