package main.java.rdg;

import main.java.DbConnection;

import java.sql.*;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class Reservation {
    private long id;
    private Long customer;
    private Long screening;
    private Timestamp begin;
    private boolean pending = true;
    private Long usedVoucher;
    private double finalPrice;
    private int child, student, adult, senior;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public Long getCustomer() {
        return customer;
    }

    public void setCustomer(Long customer) {
        this.customer = customer;
    }

    public Long getScreening() {
        return screening;
    }

    public void setScreening(Long screening) {
        this.screening = screening;
    }

    public Timestamp getBegin() {
        return begin;
    }

    public void setBegin(Timestamp begin) {
        this.begin = begin;
    }

    public boolean isPending() {
        return pending;
    }

    public void setPending(boolean pending) {
        this.pending = pending;
    }

    public Long getUsedVoucher() {
        return usedVoucher;
    }

    public void setUsedVoucher(Long usedVoucher) {
        this.usedVoucher = usedVoucher;
    }

    public double getFinalPrice() {
        return finalPrice;
    }

    public void setFinalPrice(double finalPrice) {
        this.finalPrice = finalPrice;
    }

    public int getChild() {
        return child;
    }

    public void setChild(int child) {
        this.child = child;
    }

    public int getStudent() {
        return student;
    }

    public void setStudent(int student) {
        this.student = student;
    }

    public int getAdult() {
        return adult;
    }

    public void setAdult(int adult) {
        this.adult = adult;
    }

    public int getSenior() {
        return senior;
    }

    public void setSenior(int senior) {
        this.senior = senior;
    }

    @Override
    public String toString() {
        return "Reservation{" +
                "id=" + id +
                ", customer=" + customer +
                ", screening=" + screening +
                ", begin=" + begin +
                ", pending=" + pending +
                ", usedVoucher=" + usedVoucher +
                ", finalPrice=" + finalPrice +
                '}';
    }

    public boolean create () {
        int freeTickets = Screening.freeTicketCount(screening);
        if (child + student + adult + senior > freeTickets) return false;

        DbConnection.setIsolationCommitted();

        try (PreparedStatement s = DbConnection.getCon().prepareStatement("INSERT INTO reservations (customer_id, screening_id, begin, pending) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) {
            DbConnection.disableCommit();

            s.setLong(1, customer);
            s.setLong(2, screening);
            s.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
            s.setBoolean(4, true);

            s.executeUpdate();

            try (ResultSet r = s.getGeneratedKeys()) {
                r.next();
                id = r.getLong(1);
            }

            if (id == 0) throw new SQLException("smth not gud");

            //Zarezervovat prislusne listky ziadane zakaznikom
            if (child > 0) {
                Ticket.reserveNTickets(screening, id, "CHILD", child);
            }
            if (student > 0) {
                Ticket.reserveNTickets(screening, id, "STUDENT", student);
            }
            if (adult > 0) {
                Ticket.reserveNTickets(screening, id, "ADULT", adult);
            }
            if (senior > 0) {
                Ticket.reserveNTickets(screening, id, "SENIOR", senior);
            }

            if (usedVoucher != null)
                setVoucher(id, usedVoucher);

            setFinalPriceSQL(id);

            DbConnection.getCon().commit();
        } catch (SQLException e) {
            DbConnection.rollback();
            e.printStackTrace();
            return false;
        } finally {
            DbConnection.resetCommit();
            DbConnection.resetIsolation();
        }

        return true;
    }

    public static boolean setVoucher (long id, long voucherID) {
        try (PreparedStatement s = DbConnection.getCon().prepareStatement("UPDATE reservations SET voucher_id=? WHERE reservation_id=?")){
            s.setLong(1, voucherID);
            s.setLong(2, id);

            s.executeUpdate();

            Voucher v = Voucher.read(voucherID);
            v.setUsed(true);
            v.update();

        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private static Reservation loadReservation(ResultSet r) throws SQLException {
        Reservation reservation = new Reservation();

        reservation.setId(r.getLong(1));
        reservation.setCustomer(r.getLong(2));
        reservation.setScreening(r.getLong(3));
        reservation.setBegin(r.getTimestamp(4));
        reservation.setPending(r.getBoolean(5));
        long voucherID = r.getLong(6);
        if (voucherID > 0)
            reservation.setUsedVoucher(voucherID);
        else
            reservation.setUsedVoucher(null);
        reservation.setFinalPrice(r.getDouble(7));

        try (PreparedStatement s = DbConnection.getCon().prepareStatement("SELECT price_id FROM tickets WHERE reservation_id=?")){
            s.setLong(1, reservation.getId());

            try (ResultSet rr = s.executeQuery()){
                while (rr.next()) {
                    Price price = Price.read(rr.getLong(1));
                    if (price.getCustomerType().equals("CHILD")){
                        reservation.child++;
                    }
                    if (price.getCustomerType().equals("STUDENT")){
                        reservation.student++;
                    }
                    if (price.getCustomerType().equals("ADULT")){
                        reservation.adult++;
                    }
                    if (price.getCustomerType().equals("SENIOR")){
                        reservation.senior++;
                    }
                }
            }

        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }

        return reservation;
    }

    public static Reservation read (long id) {
        try (PreparedStatement s = DbConnection.getCon().prepareStatement("SELECT * FROM reservations WHERE reservation_id=?")){
            s.setLong(1, id);

            try (ResultSet r = s.executeQuery()) {
                r.next();
                return loadReservation(r);
            }

        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static boolean confirmReservation (long reservationID) {
        Reservation r = read(reservationID);
        Timestamp limit = new Timestamp(r.getBegin().getTime());
        limit.setTime(limit.getTime() + TimeUnit.MINUTES.toMillis(10));

        Timestamp now = new Timestamp(System.currentTimeMillis());

        if (now.after(limit)){
            Ticket.unreserveTickets(reservationID);
            Reservation reservation = Reservation.read(reservationID);
            reservation.delete();
            return false;
        }

        try (PreparedStatement s = DbConnection.getCon().prepareStatement("UPDATE reservations set pending=false WHERE reservation_id=?")) {
            s.setLong(1, reservationID);
            s.executeUpdate();
        } catch (SQLException e) {
            return false;
        }
        return true;
    }

    public static void setFinalPriceSQL (long reservationID) {
        try (PreparedStatement s = DbConnection.getCon().prepareStatement("UPDATE reservations SET finalprice=? WHERE reservation_id=?")) {

            Set<Ticket> tickets = getTickets(reservationID);

            double price = 0;

            for (Ticket ticket : tickets) {
                price += Price.read(ticket.getPrice()).getValue();
            }

            Reservation res = read(reservationID);
            if (res.getUsedVoucher() != null) {
                price -= Math.min(Voucher.read(res.getUsedVoucher()).getDiscountValue(), price);
            }

            s.setDouble(1, price);
            s.setLong(2, reservationID);
            s.executeUpdate();

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

    public static Set<Reservation> listAll () {
        try (PreparedStatement s = DbConnection.getCon().prepareStatement("SELECT * FROM reservations")){
            Set<Reservation> reservations = new HashSet<>();

            try (ResultSet r = s.executeQuery()) {
                while (r.next()) {
                    reservations.add(loadReservation(r));
                }
            }

            return reservations;
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Set<Ticket> getTickets (long id) {
        try (PreparedStatement s = DbConnection.getCon().prepareStatement("SELECT * FROM tickets WHERE reservation_id=?")){
            Set<Ticket> tickets = new HashSet<>();

            s.setLong(1, id);

            try (ResultSet r = s.executeQuery()) {
                while (r.next()) {
                    Ticket t = new Ticket();

                    t.setId(r.getLong(1));
                    t.setScreening(r.getLong(2));
                    t.setSeat(r.getLong(3));
                    t.setState(r.getString(4));
                    t.setReservation(id);
                    t.setPrice(r.getLong(6));

                    tickets.add(t);
                }
            }

            return tickets;
        } catch (SQLException e){
            e.printStackTrace();
            return null;
        }
    }

    public static Set<Reservation> readCustomersReservations (long id) {
        try (PreparedStatement s = DbConnection.getCon().prepareStatement("SELECT * FROM reservations WHERE customer_id=?")) {
            s.setLong(1, id);

            try (ResultSet r = s.executeQuery()) {
                Set<Reservation> reservations = new HashSet<>();

                while (r.next()) {
                    reservations.add(loadReservation(r));
                }

                return reservations;
            }

        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    public boolean update () {
        DbConnection.setIsolationCommitted();
        try {
            DbConnection.disableCommit();

            Set<Ticket> tickets = getTickets(id);

            int oldChild = tickets.stream().filter(x -> Price.read(x.getPrice()).getCustomerType().equals("CHLID")).toList().size();
            int oldStudent = tickets.stream().filter(x -> Price.read(x.getPrice()).getCustomerType().equals("STUDENT")).toList().size();
            int oldAdult = tickets.stream().filter(x -> Price.read(x.getPrice()).getCustomerType().equals("ADULT")).toList().size();
            int oldSenior = tickets.stream().filter(x -> Price.read(x.getPrice()).getCustomerType().equals("SENIOR")).toList().size();

            if (oldChild == child && oldStudent == student && oldAdult == adult && oldSenior == senior)
                return true;


            if (child < oldChild) {
                Ticket.unreserveNTickets(screening, id, "CHILD", oldChild - child);
            }
            if (oldChild < child) {
                if (Screening.freeTicketCount(screening) < child - oldChild)
                    return false;

                Ticket.reserveNTickets(screening, id, "CHILD", child - oldChild);
            }

            if (student < oldStudent) {
                Ticket.unreserveNTickets(screening, id, "STUDENT", oldStudent - student);
            }
            if (oldStudent < student) {
                if (Screening.freeTicketCount(screening) < student - oldStudent)
                    return false;

                Ticket.reserveNTickets(screening, id, "STUDENT", student - oldStudent);
            }

            if (adult < oldAdult) {
                Ticket.unreserveNTickets(screening, id, "ADULT", oldAdult - adult);
            }
            if (oldAdult < adult) {
                if (Screening.freeTicketCount(screening) < adult - oldAdult)
                    return false;

                Ticket.reserveNTickets(screening, id, "ADULT", adult - oldAdult);
            }

            if (senior < oldSenior) {
                Ticket.unreserveNTickets(screening, id, "SENIOR", oldSenior - senior);
            }
            if (oldSenior < senior) {
                if (Screening.freeTicketCount(screening) < senior - oldSenior)
                    return false;

                Ticket.reserveNTickets(screening, id, "SENIOR", senior - oldSenior);
            }

            setFinalPrice(id);

            DbConnection.getCon().commit();
        } catch (SQLException e) {
            DbConnection.rollback();
            e.printStackTrace();
            return false;
        } finally {
            DbConnection.resetCommit();
            DbConnection.resetIsolation();
        }
        return true;
    }

    public boolean delete () {
        try (PreparedStatement s = DbConnection.getCon().prepareStatement("DELETE FROM reservations WHERE reservation_id=?")){
            s.setLong(1, id);
            s.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public static boolean clearOvertimeReservations () {
        Timestamp limit = new Timestamp(System.currentTimeMillis());
        limit.setTime(limit.getTime() - TimeUnit.MINUTES.toMillis(10));

        try (PreparedStatement s = DbConnection.getCon().prepareStatement("SELECT reservation_id, voucher_id FROM reservations WHERE begin < ? AND pending=true")){
            DbConnection.disableCommit();

            s.setTimestamp(1, limit);
            try (ResultSet r = s.executeQuery()){
                while (r.next()){
                    long reservationID = r.getLong(1);
                    Long voucherID = r.getLong(2);

                    if (voucherID != null){
                        Voucher v = Voucher.read(voucherID);
                        if (v != null) {
                            v.setUsed(false);
                            v.update();

                        }
                    }
                    Ticket.unreserveTickets(reservationID);
                    Reservation.read(reservationID).delete();
                }
            }

            DbConnection.getCon().commit();
        } catch (SQLException e){
            DbConnection.rollback();
            e.printStackTrace();
            return false;
        } finally {
            DbConnection.resetCommit();
            DbConnection.resetIsolation();
        }
        return true;
    }
}
