package org.example.petshopdesktop.controllers;

import javafx.application.Platform;
import org.example.petshopdesktop.auth.UserSession;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;

import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.AppointmentApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController;
import org.example.petshopdesktop.ui.CalendarPane;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TableViewSupport;

import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import java.util.Comparator;
import java.util.stream.Collectors;

public class AppointmentController {

    @FXML private TableView<AppointmentDTO> tvAppointments;

    @FXML private TableColumn<AppointmentDTO,Integer> colAppointmentId;
    @FXML private TableColumn<AppointmentDTO,String> colPetName;
    @FXML private TableColumn<AppointmentDTO,String> colServiceName;
    @FXML private TableColumn<AppointmentDTO,String> colAppointmentDate;
    @FXML private TableColumn<AppointmentDTO,String> colAppointmentTime;
    @FXML private TableColumn<AppointmentDTO,String> colCustomerName;
    @FXML private TableColumn<AppointmentDTO,String> colEmployeeName;
    @FXML private TableColumn<AppointmentDTO,String> colAppointmentStatus;
    @FXML private TableColumn<AppointmentDTO,String> colStoreName;

    @FXML private Button btnAdd;
    @FXML private Button btnEdit;
    @FXML private Button btnDelete;
    @FXML private Button btnRefresh;
    @FXML private javafx.scene.control.ToggleButton btnMyAppointments;

    @FXML private Label lblStatus;

    @FXML private TextField txtSearch;

    @FXML private ComboBox<String> cbStatusFilter;
    @FXML private ComboBox<DropdownOption> cbStoreFilter;

    private final java.util.ArrayList<DropdownOption> storeOptions = new java.util.ArrayList<>();

    @FXML private CalendarPane calendarPane;
    private LocalDate selectedCalendarDate = null;

    private final ObservableList<AppointmentDTO> appointments = FXCollections.observableArrayList();
    private FilteredList<AppointmentDTO> filtered;

    @FXML
    public void initialize(){
        btnEdit.setDisable(true);
        btnDelete.setDisable(true);
        tvAppointments.getSelectionModel().setSelectionMode(javafx.scene.control.SelectionMode.MULTIPLE);

        colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
        colPetName.setCellValueFactory(new PropertyValueFactory<>("petName"));
        colServiceName.setCellValueFactory(new PropertyValueFactory<>("serviceName"));
        colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate"));
        colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime"));
        colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
        colEmployeeName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
        colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus"));
        colStoreName.setCellValueFactory(new PropertyValueFactory<>("storeName"));

        filtered = new FilteredList<>(appointments, a -> true);
        TableViewSupport.bindSortedItems(tvAppointments, filtered);
        TableViewSupport.installDoubleClickAction(tvAppointments, selected -> openDialog(selected, "Edit"));

        if (UserSession.getInstance().isStaff()) {
            btnMyAppointments.setVisible(true);
            btnMyAppointments.setManaged(true);
        }

        cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Booked", "Completed", "Missed", "Cancelled"));
        cbStatusFilter.getSelectionModel().selectFirst();
        cbStatusFilter.valueProperty().addListener((obs, o, n) -> applyFilterPredicate());

        if (UserSession.getInstance().isAdmin()) {
            cbStoreFilter.setVisible(true);
            cbStoreFilter.setManaged(true);
            loadStoreFilter();
        }
        cbStoreFilter.valueProperty().addListener((obs, o, n) -> loadAppointments());

        if (txtSearch != null) {
            txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
        }

        tvAppointments.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
            boolean hasSelection = newValue != null;
            btnEdit.setDisable(!hasSelection);
            btnDelete.setDisable(!hasSelection);
        });

        tvAppointments.setOnKeyPressed(event -> {
            if (event.getCode() == javafx.scene.input.KeyCode.DELETE) {
                if (tvAppointments.getSelectionModel().getSelectedItem() != null) {
                    btnDeleteClicked(null);
                }
            }
        });

        loadAppointments();

        calendarPane.setOnDateSelected(date -> {
            selectedCalendarDate = date;
            applyFilterPredicate();
        });
    }

    @FXML
    void btnMyAppointmentsToggled(javafx.event.ActionEvent event) {
        loadAppointments();
    }

    private void loadAppointments(){
        new Thread(() -> {
            try{
                Long storeId = selectedStoreId();
                Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null;
                List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null, storeId, employeeId);
                List<AppointmentDTO> appointmentDTOs = responses.stream()
                    .map(this::mapToAppointmentDTO)
                    .sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
                    .collect(Collectors.toList());

                Platform.runLater(() -> {
                    appointments.setAll(appointmentDTOs);
                    Set<LocalDate> dates = appointmentDTOs.stream()
                        .filter(a -> a.getAppointmentDate() != null && !a.getAppointmentDate().isEmpty())
                        .map(a -> { try { return LocalDate.parse(a.getAppointmentDate()); } catch (Exception ex) { return null; } })
                        .filter(d -> d != null)
                        .collect(java.util.stream.Collectors.toSet());
                    calendarPane.setEventDates(dates);
                    applyFilterPredicate();
                });
            }catch(Exception e){
                Platform.runLater(() -> {
                    ActivityLogger.getInstance().logException(
                        "AppointmentController.loadAppointments",
                        e,
                        "Loading appointments for table display");
                    e.printStackTrace();
                });
            }
        }).start();
    }

    private void applyFilter(String text) {
        String query = text == null || text.trim().isEmpty() ? null : text.trim();
        new Thread(() -> {
            try {
                Long storeId = selectedStoreId();
                Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null;
                List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query, storeId, employeeId);
                List<AppointmentDTO> appointmentDTOs = responses.stream()
                    .map(this::mapToAppointmentDTO)
                    .sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
                    .collect(Collectors.toList());

                Platform.runLater(() -> {
                    appointments.setAll(appointmentDTOs);
                    Set<LocalDate> dates = appointmentDTOs.stream()
                        .filter(a -> a.getAppointmentDate() != null && !a.getAppointmentDate().isEmpty())
                        .map(a -> { try { return LocalDate.parse(a.getAppointmentDate()); } catch (Exception ex) { return null; } })
                        .filter(d -> d != null)
                        .collect(java.util.stream.Collectors.toSet());
                    calendarPane.setEventDates(dates);
                    applyFilterPredicate();
                });
            } catch (Exception e) {
                Platform.runLater(() -> {
                    ActivityLogger.getInstance().logException(
                        "AppointmentController.applyFilter",
                        e,
                        String.format("Filtering appointments with query: %s", query));
                    e.printStackTrace();
                });
            }
        }).start();
    }

    @FXML
    void btnRefresh(ActionEvent event) {
        txtSearch.clear();
        cbStatusFilter.getSelectionModel().selectFirst();
        cbStoreFilter.getSelectionModel().selectFirst();
        selectedCalendarDate = null;
        filtered.setPredicate(a -> true);
        tvAppointments.getSortOrder().clear();
        loadAppointments();
        TableViewSupport.flashStatus(lblStatus, "Refreshed");
    }

    @FXML
    void btnAddClicked(ActionEvent event){
        openDialog(null, "Add");
    }

    @FXML
    void btnEditClicked(ActionEvent event){

        AppointmentDTO selected =
                tvAppointments.getSelectionModel().getSelectedItem();

        if(selected == null){
            showAlert("Select Appointment", "Please select appointment to edit.");
            return;
        }

        openDialog(selected, "Edit");
    }

    @FXML
    void btnDeleteClicked(ActionEvent event){
        
        var selectedAppointments = tvAppointments.getSelectionModel().getSelectedItems();
        if (selectedAppointments.isEmpty()) return;

        Alert question = new Alert(Alert.AlertType.CONFIRMATION);
        question.setHeaderText("Please confirm delete");
        String message = selectedAppointments.size() == 1
            ? "Are you sure you want to delete this appointment?"
            : "Are you sure you want to delete " + selectedAppointments.size() + " appointments?";
        question.setContentText(message);
        question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
        java.util.Optional<ButtonType> result = question.showAndWait();

        if (result.isPresent() && result.get() == ButtonType.OK) {
            List<Long> ids = selectedAppointments.stream()
                .map(a -> (long) a.getAppointmentId())
                .collect(Collectors.toList());

            try {
                AppointmentApi.getInstance().deleteAppointments(ids);
                Alert alert = new Alert(Alert.AlertType.INFORMATION);
                alert.setHeaderText("Database Operation Confirmed");
                alert.setContentText("Successfully deleted " + ids.size() + " appointment(s)");
                alert.showAndWait();
            } catch (Exception e) {
                ActivityLogger.getInstance().logException(
                    "AppointmentController.btnDeleteClicked",
                    e,
                    "Deleting appointments");
                Alert alert = new Alert(Alert.AlertType.ERROR);
                alert.setHeaderText("Delete Operation Failed");
                alert.setContentText(e.getMessage());
                alert.showAndWait();
            }

            loadAppointments();
        }
    }

    private void openDialog(AppointmentDTO appt, String mode){

        try{
            FXMLLoader loader = new FXMLLoader(
                    getClass().getResource(
                            "/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml"
                    )
            );

            Scene scene = new Scene(loader.load());

            AppointmentDialogController controller =
                    loader.getController();

            controller.setMode(mode);

            if(mode.equals("Edit")){
                controller.displayAppointmentDetails(appt);
            }

            Stage stage = new Stage();
            stage.initModality(Modality.APPLICATION_MODAL);
            stage.setScene(scene);
            stage.showAndWait();

            loadAppointments();

        }catch(Exception e){
            ActivityLogger.getInstance().logException(
                "AppointmentController.openDialog",
                e,
                "Opening appointment dialog in " + mode + " mode");
            e.printStackTrace();
        }
    }

    private void loadStoreFilter() {
        new Thread(() -> {
            try {
                List<DropdownOption> stores = DropdownApi.getInstance().getStores();
                DropdownOption allStores = new DropdownOption();
                allStores.setLabel("All Stores");
                Platform.runLater(() -> {
                    storeOptions.clear();
                    storeOptions.addAll(stores);
                    java.util.List<DropdownOption> items = new java.util.ArrayList<>();
                    items.add(allStores);
                    items.addAll(stores);
                    cbStoreFilter.setItems(FXCollections.observableArrayList(items));
                    cbStoreFilter.getSelectionModel().selectFirst();
                });
            } catch (Exception e) {
                ActivityLogger.getInstance().logException("AppointmentController.loadStoreFilter", e, "Loading store filter");
            }
        }).start();
    }

    private Long selectedStoreId() {
        if (!UserSession.getInstance().isAdmin()) return UserSession.getInstance().getStoreId();
        DropdownOption selected = cbStoreFilter.getValue();
        return (selected != null && selected.getId() != null) ? selected.getId() : null;
    }

    private void applyFilterPredicate() {
        String selectedStatus = cbStatusFilter.getValue();
        filtered.setPredicate(apt -> {
            boolean dateMatch = selectedCalendarDate == null
                || apt.getAppointmentDate().equals(selectedCalendarDate.toString());
            boolean statusMatch = selectedStatus == null || selectedStatus.equals("All Statuses")
                || apt.getAppointmentStatus().equalsIgnoreCase(selectedStatus);
            return dateMatch && statusMatch;
        });
    }

    private void showAlert(String title, String msg){
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle(title);
        alert.setHeaderText(null);
        alert.setContentText(msg);
        alert.showAndWait();
    }

    private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
        return new AppointmentDTO(
            response.getAppointmentId().intValue(),
            response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
            response.getCustomerName() != null ? response.getCustomerName() : "",
            response.getPetId() != null ? response.getPetId().intValue() : 0,
            response.getPetName() != null ? response.getPetName() : "",
            response.getServiceId() != null ? response.getServiceId().intValue() : 0,
            response.getServiceName() != null ? response.getServiceName() : "",
            response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
            response.getEmployeeName() != null ? response.getEmployeeName() : "",
            response.getAppointmentDate() != null ? response.getAppointmentDate().toString() : "",
            response.getAppointmentTime() != null ? response.getAppointmentTime().toString() : "",
            normalizeAppointmentStatus(response.getAppointmentStatus()),
            response.getStoreName(),
            response.getStoreId()
        );
    }

    private String normalizeAppointmentStatus(String status) {
        if (status == null) {
            return "Booked";
        }
        return switch (status.trim().toLowerCase()) {
            case "booked" -> "Booked";
            case "completed" -> "Completed";
            case "missed" -> "Missed";
            case "cancelled", "canceled" -> "Cancelled";
            default -> "Booked";
        };
    }
}
