import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Pet Care Scheduler
 *
 * @author John Ahlroos / 2025
 *
 * Allows the user to manage Pet care appointments
 */
public class PetCareScheduler {

    private final List<Pet> pets;
    private final String dataFileName;
    private final Scanner scanner;

    public PetCareScheduler(Scanner scanner, String dataFileName) {
        this.dataFileName = dataFileName;
        this.scanner = scanner;
        this.pets = new ArrayList<>();
    }

    public static void main(String[] args) {

        System.out.println("PET CARE SCHEDULER v1.0");
        System.out.println("=================================================================");
        System.out.println();

        try (Scanner scanner = new Scanner(System.in)) {

            PetCareScheduler scheduler = new PetCareScheduler(scanner, "data.txt");

            boolean loaded = scheduler.loadPetsFromFile();
            if (!loaded) {
                System.err.println("Failed to load data file.");
                return;
            }

            System.out.println();


            boolean exited = false;
            while (!exited) {
                System.out.println("Actions:");
                System.out.println("\t1: Register Pet");
                System.out.println("\t2: Schedule Appointment");
                System.out.println("\t3: List Pets");
                System.out.println("\t4: Print Report");
                System.out.println("\t5: Save & Exit");
                System.out.println();
                System.out.print("Action: ");

                try  {
                    int action = Integer.parseInt(scanner.nextLine());
                    switch (action) {
                        case 1:
                            scheduler.registerPet();
                            break;
                        case 2:
                            scheduler.addAppointment();
                            break;
                        case 3:
                            scheduler.list();
                            break;
                        case 4:
                            scheduler.report();
                            break;
                        case 5:
                            boolean saved = scheduler.savePetsToFile();
                            if (!saved) {
                                System.err.println("Failed to save data file! Latest changes were lost.");
                                System.out.print("Exit anyway (yes/no)?");
                                String option = scanner.nextLine();
                                exited = option.equals("yes");
                            } else {
                                exited = true;
                            }
                            break;
                        default:
                            System.out.println("Not a valid option!");
                    }
                } catch (NumberFormatException nfe) {
                    System.err.println("Action must be a number!");
                }
            }
        } catch (RuntimeException re) {
            System.err.println(re.getMessage());
        }
    }

    /**
     * Register a new pet with the system
     */
    public void registerPet() {
        System.out.println("=================================================================");

        String id = UUID.randomUUID().toString();
        LocalDate registration = LocalDate.now();
        System.out.print("Name: ");
        String name = scanner.nextLine();
        if (name.isBlank()) {
            System.err.println("Name must not be blank!");
            return;
        }

        System.out.print("Owner: ");
        String owner = scanner.nextLine();
        if (owner.isBlank()) {
            System.err.println("Owner must not be blank!");
            return;
        }

        System.out.print("Contact Information: ");
        String contact = scanner.nextLine();
        System.out.print("Breed: ");
        String breed = scanner.nextLine();
        System.out.print("Age: ");
        int age;
        try{
            age = Integer.parseInt(scanner.nextLine());
        } catch (NumberFormatException nfe) {
            System.err.println("Not a valid number!");
            return;
        }

        Pet pet = new Pet(id,name,owner,contact,breed,age,registration);
        pets.add(pet);

        System.out.printf("New pet %s registered with id %s%n", pet.getName(), pet.getId());
        System.out.println("=================================================================");
        System.out.println();
    }

    /**
     * Add a new appointment for a pet
     */
    public void addAppointment() {
        System.out.println("=================================================================");
        System.out.print("Pet ID: ");
        String petId = scanner.nextLine();

        Optional<Pet> pet = pets.stream().filter(p -> p.getId().equals(petId)).findFirst();
        if (pet.isEmpty()) {
            System.err.printf("No Pet with id %s%n", petId);
            return;
        }

        System.out.printf("Type (%s): ", Arrays.stream(Appointment.Type.values()).map(Enum::name).collect(Collectors.joining("|")));
        String typeStr = scanner.nextLine().toUpperCase();
        if (Arrays.stream(Appointment.Type.values()).map(Enum::name).noneMatch(t -> t.equals(typeStr))) {
            System.err.printf("%s is not a valid appointment type%n", typeStr);
            return;
        }

        System.out.print("Date (dd/mm/yyyy): ");
        String dateStr = scanner.nextLine();
        LocalDate date;
        try {
            date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("dd/MM/yyyy"));
        } catch (DateTimeParseException dtpe) {
            System.err.printf("%s is not a valid date%n", dateStr);
            return;
        }

        System.out.print("Time (hh:mm): ");
        String timeStr = scanner.nextLine();
        LocalTime time;
        try {
            time = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm"));
        } catch (DateTimeParseException dtpe) {
            System.err.printf("%s is not a valid time%n", timeStr);
            return;
        }

        System.out.print("Notes: ");
        String notes = scanner.nextLine();

        Appointment appointment = new Appointment(Appointment.Type.valueOf(typeStr), date, time, notes);
        pet.get().addAppointment(appointment);

        System.out.printf("Appointment added for pet %s at %s%n", pet.get().getName(), appointment.getDate().atTime(appointment.getTime()));
        System.out.println("=================================================================");
        System.out.println();
    }

    /**
     * List all pets in the system
     */
    public void list() {
        System.out.println("=================================================================");
        System.out.println();

        for (Pet pet: pets) {
            System.out.printf("Id:\t\t\t%s%n", pet.getId());
            System.out.printf("Name:\t\t%s%n", pet.getName());
            System.out.printf("Owner:\t\t%s%n", pet.getOwner());
            System.out.printf("Contact:\t%s%n", pet.getContactInformation());
            System.out.printf("Breed:\t\t%s%n", pet.getBreed());
            System.out.printf("Age:\t\t%d%n", pet.getAge());
            System.out.printf("Registered:\t%s%n", pet.getRegistrationDate());

            System.out.println("Upcoming Appointments:");
            for (Appointment appointment : pet.getAppointments()) {
                if (appointment.getDate().atTime(appointment.getTime()).isAfter(LocalDateTime.now())) {
                    System.out.printf("\t- %s %s %s%n",
                            appointment.getDate().atTime(appointment.getTime()),
                            appointment.getType(),
                            appointment.getNotes());
                }
            }

            System.out.println("Past Appointments:");
            for (Appointment appointment : pet.getAppointments()) {
                if (appointment.getDate().atTime(appointment.getTime()).isBefore(LocalDateTime.now())) {
                    System.out.printf("\t- %s %s %s%n",
                            appointment.getDate().atTime(appointment.getTime()),
                            appointment.getType(),
                            appointment.getNotes());
                }
            }

            System.out.println();
        }

        System.out.println("=================================================================");
        System.out.println();
    }

    /**
     * Generate a report of future and past appointments
     */
    public void report() {
        System.out.println("=================================================================");
        System.out.println("Next Week: ");
        LocalDateTime nextMonday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).atTime(LocalTime.MIDNIGHT);
        LocalDateTime nextSunday = nextMonday.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
        for (Pet pet : pets) {
            for (Appointment appointment : pet.getAppointments()) {
                LocalDateTime dt = appointment.getDate().atTime(appointment.getTime());
                if (dt.isAfter(nextMonday) && dt.isBefore(nextSunday)) {
                    System.out.printf("%s: %s %s %s%n", pet.getId(), dt, pet.getName(), pet.getOwner());
                }
            }
        }

        System.out.println();

        System.out.println("Due Visits: ");
        LocalDateTime dueDate = LocalDate.now().minusMonths(6).atTime(LocalTime.MIDNIGHT);
        for (Pet pet : pets) {
            for (Appointment appointment : pet.getAppointments()) {
                LocalDateTime dt = appointment.getDate().atTime(appointment.getTime());
                if (dt.isBefore(dueDate)) {
                    System.out.printf("%s: %s %s %s%n", pet.getId(), dt, pet.getName(), pet.getOwner());
                }
            }
        }

        System.out.println("=================================================================");
        System.out.println();
    }

    /**
     * Load pets from file
     */
    private boolean loadPetsFromFile()  {
        File dataFile = new File(".", dataFileName);
        if (!dataFile.exists()) {
            System.out.println("No data file exists, loading empty pet database");
            return true;
        }

        try (BufferedReader reader = Files.newBufferedReader(dataFile.toPath())) {
            String line;
            while((line = reader.readLine()) != null) {
                StringTokenizer petDeserializer = new StringTokenizer(line, "|");
                Pet pet = new Pet(
                    petDeserializer.nextToken(), // id
                    petDeserializer.nextToken(), // name
                    petDeserializer.nextToken(), // owner
                    petDeserializer.nextToken(), // contactInformation
                    petDeserializer.nextToken(), // breed
                    Integer.parseInt(petDeserializer.nextToken()), //age
                    LocalDate.parse(petDeserializer.nextToken(), DateTimeFormatter.ISO_DATE) // registrationDate
                );

                while((line = reader.readLine()) != null) {
                    if (line.isEmpty()) {
                        break;
                    }

                    StringTokenizer appointmentDeserializer = new StringTokenizer(line, "|");
                    Appointment appointment = new Appointment(
                        Appointment.Type.valueOf(appointmentDeserializer.nextToken()), // type
                        LocalDate.parse(appointmentDeserializer.nextToken(), DateTimeFormatter.ISO_DATE),   // date
                        LocalTime.parse(appointmentDeserializer.nextToken(), DateTimeFormatter.ISO_TIME),   // time
                        appointmentDeserializer.hasMoreTokens() ? appointmentDeserializer.nextToken() : ""  // notes
                    );
                    pet.addAppointment(appointment);
                }

                pets.add(pet);
            }
            System.out.printf("%d pets loaded successfully from data file %s.%n", pets.size(), dataFileName);
            return true;
        } catch (IOException | RuntimeException e) {
            System.err.printf("Failed to read data file. %s%n", e.getMessage());
            return false;
        }
    }

    /**
     * Save pets to file
     */
    private boolean savePetsToFile() {
        if (pets == null || pets.isEmpty()) {
            System.out.println("No pets to save");
            return true;
        }

        File dataFile = new File(".", dataFileName);

        try {
            Path parent = dataFile.toPath().getParent();
            if (parent != null) {
                Files.createDirectories(parent);
            }
        } catch (IOException e) {
            System.err.println("Failed to create data directory");
            return false;
        }

        OpenOption[] fileOptions = new OpenOption[]{ StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
        try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(dataFile.toPath(), fileOptions))) {
            for (Pet pet : pets) {
                StringJoiner petSerializer = new StringJoiner("|");
                petSerializer.add(Objects.toString(pet.getId(), UUID.randomUUID().toString()));
                petSerializer.add(Objects.toString(pet.getName()," "));
                petSerializer.add(Objects.toString(pet.getOwner(), " "));
                petSerializer.add(Objects.toString(pet.getContactInformation(), "None"));
                petSerializer.add(Objects.toString(pet.getBreed(), "Unknown"));
                petSerializer.add(String.valueOf(pet.getAge()));
                petSerializer.add(Optional.ofNullable(pet.getRegistrationDate()).orElse(LocalDate.now()).format(DateTimeFormatter.ISO_DATE));
                writer.println(petSerializer);

                for (Appointment appointment : pet.getAppointments()) {
                    StringJoiner appointmentSerializer = new StringJoiner("|");
                    appointmentSerializer.add(appointment.getType().name());
                    appointmentSerializer.add(appointment.getDate().format(DateTimeFormatter.ISO_DATE));
                    appointmentSerializer.add(appointment.getTime().format(DateTimeFormatter.ISO_TIME));
                    appointmentSerializer.add(appointment.getNotes());
                    writer.println(appointmentSerializer);
                }

                writer.println();
            }
            System.out.printf("%d pets saved successfully to fata file %s.%n", pets.size(), dataFileName);
            return true;
        } catch (IOException | RuntimeException e) {
            System.err.printf("Failed to write to file. %s%n", e.getMessage());
            return false;
        }
    }
}