package mk.finki.roomreservation.service;

import mk.finki.roomreservation.model.CreateReservationRequest;
import mk.finki.roomreservation.model.EquipmentRequest;
import mk.finki.roomreservation.model.ReservationDetails;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Date;
import java.sql.Time;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.Objects;

@Service
public class ReservationService {

    private final JdbcTemplate jdbcTemplate;

    public ReservationService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional
    public ReservationDetails createReservation(CreateReservationRequest request) {
        validateCreateRequest(request);

        LocalDate date = LocalDate.parse(request.reservationDate());
        LocalTime start = LocalTime.parse(request.startTime());
        LocalTime end = LocalTime.parse(request.endTime());

        List<EquipmentRequest> equipment =
                request.equipment() == null ? List.of() : request.equipment();

        String insertReservationSql = """
                INSERT INTO project.reservations (
                    room_id,
                    user_id,
                    reservation_date,
                    start_time,
                    end_time,
                    status
                )
                VALUES (?, ?, ?, ?, ?, 'pending')
                RETURNING reservation_id
                """;

        Integer reservationId = jdbcTemplate.queryForObject(
                insertReservationSql,
                Integer.class,
                request.roomId(),
                request.userId(),
                Date.valueOf(date),
                Time.valueOf(start),
                Time.valueOf(end)
        );

        if (reservationId == null) {
            throw new RuntimeException("Reservation was not created.");
        }

        for (EquipmentRequest item : equipment) {
            jdbcTemplate.update(
                    """
                    INSERT INTO project.reservation_equipment (
                        reservation_id,
                        equipment_id,
                        requested_quantity
                    )
                    VALUES (?, ?, ?)
                    """,
                    reservationId,
                    item.equipmentId(),
                    item.requestedQuantity()
            );
        }

        return findReservationDetails(reservationId);
    }

    public ReservationDetails findReservationDetails(int reservationId) {
        String sql = """
                SELECT
                    res.reservation_id,
                    u.full_name AS requester_name,
                    COALESCE(r.room_code, 'No room') AS room_code,
                    res.reservation_date::text AS reservation_date,
                    res.start_time::text AS start_time,
                    res.end_time::text AS end_time,
                    res.status,
                    COALESCE(eq.requested_equipment, 'No equipment') AS equipment
                FROM project.reservations res
                JOIN project.users u
                    ON res.user_id = u.user_id
                LEFT JOIN project.rooms r
                    ON res.room_id = r.room_id
                LEFT JOIN (
                    SELECT
                        req.reservation_id,
                        string_agg(e.name || ' x' || req.requested_quantity, ', ' ORDER BY e.name) AS requested_equipment
                    FROM project.reservation_equipment req
                    JOIN project.equipment e
                        ON req.equipment_id = e.equipment_id
                    GROUP BY req.reservation_id
                ) eq
                    ON res.reservation_id = eq.reservation_id
                WHERE res.reservation_id = ?
                """;

        ReservationDetails details = jdbcTemplate.queryForObject(
                sql,
                (rs, rowNum) -> new ReservationDetails(
                        rs.getInt("reservation_id"),
                        rs.getString("requester_name"),
                        rs.getString("room_code"),
                        rs.getString("reservation_date"),
                        rs.getString("start_time"),
                        rs.getString("end_time"),
                        rs.getString("status"),
                        rs.getString("equipment")
                ),
                reservationId
        );

        if (details == null) {
            throw new RuntimeException("Reservation not found.");
        }

        return details;
    }

    private void validateCreateRequest(CreateReservationRequest request) {
        if (request.userId() == null) {
            throw new IllegalArgumentException("Requester user is required.");
        }

        if (request.reservationDate() == null || request.reservationDate().isBlank()) {
            throw new IllegalArgumentException("Reservation date is required.");
        }

        if (request.startTime() == null || request.startTime().isBlank()) {
            throw new IllegalArgumentException("Start time is required.");
        }

        if (request.endTime() == null || request.endTime().isBlank()) {
            throw new IllegalArgumentException("End time is required.");
        }

        LocalTime start = LocalTime.parse(request.startTime());
        LocalTime end = LocalTime.parse(request.endTime());

        if (!end.isAfter(start)) {
            throw new IllegalArgumentException("End time must be after start time.");
        }

        boolean hasRoom = request.roomId() != null;
        boolean hasEquipment = request.equipment() != null && !request.equipment().isEmpty();

        if (!hasRoom && !hasEquipment) {
            throw new IllegalArgumentException("Reservation must include at least one resource: room or equipment.");
        }

        if (hasEquipment) {
            for (EquipmentRequest item : request.equipment()) {
                Objects.requireNonNull(item, "Equipment request cannot be null.");

                if (item.equipmentId() <= 0) {
                    throw new IllegalArgumentException("Equipment ID must be valid.");
                }

                if (item.requestedQuantity() <= 0) {
                    throw new IllegalArgumentException("Requested equipment quantity must be greater than 0.");
                }
            }
        }
    }
}