package com.gnx.telemedicine.controller;

import com.gnx.telemedicine.dto.appointment.AppointmentRequestDto;
import com.gnx.telemedicine.dto.appointment.AppointmentResponseDto;
import com.gnx.telemedicine.exception.ForbiddenException;
import com.gnx.telemedicine.exception.ResourceNotFoundException;
import com.gnx.telemedicine.model.Appointment;
import com.gnx.telemedicine.model.Doctor;
import com.gnx.telemedicine.model.Patient;
import com.gnx.telemedicine.model.UserModel;
import com.gnx.telemedicine.model.enums.AppointmentStatus;
import com.gnx.telemedicine.repository.DoctorRepository;
import com.gnx.telemedicine.repository.PatientRepository;
import com.gnx.telemedicine.repository.UserRepository;
import com.gnx.telemedicine.service.AppointmentService;
import com.gnx.telemedicine.util.InputSanitizer;
import lombok.extern.slf4j.Slf4j;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@RestController
@RequestMapping("/api/v3/appointments")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "Appointments", description = "Endpoints for managing appointments")
@SecurityRequirement(name = "bearerAuth")
public class AppointmentController {
    private final AppointmentService appointmentService;
    private final UserRepository userRepository;
    private final DoctorRepository doctorRepository;
    private final PatientRepository patientRepository;

    @Operation(
            summary = "Get appointments by patient ID",
            description = "Get appointments for a specific patient. Requires authentication.",
            parameters = @Parameter(name = "id", description = "Patient ID", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "List of appointments for the patient retrieved successfully.",
                    content = @Content(
                            mediaType = "application/json",
                            array = @ArraySchema(schema = @Schema(implementation = AppointmentResponseDto.class))
                    )
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "Unauthorized - Missing or expired JWT"
            )
    })
    @GetMapping("/patient/{id}")
    public ResponseEntity<List<AppointmentResponseDto>> getAppointmentsByPatientId(
            Authentication authentication,
            @PathVariable UUID id) {
        String userEmail = authentication.getName();
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));
        
        // Check if user is the patient or the patient's treating doctor
        if (user.getRole().name().equals("PATIENT")) {
            Optional<Patient> patientOpt = patientRepository.findByUser(user);
            if (patientOpt.isEmpty()) {
                throw new ForbiddenException("Patient profile not found for user");
            }
            Patient patient = patientOpt.get();
            if (!patient.getId().equals(id)) {
                throw new ForbiddenException("Patients can only view their own appointments");
            }
            return ResponseEntity.ok(appointmentService.getAppointmentsByPatientId(id));
        } else if (user.getRole().name().equals("DOCTOR")) {
            Optional<Doctor> doctorOpt = doctorRepository.findByUser(user);
            if (doctorOpt.isEmpty()) {
                throw new ForbiddenException("Doctor profile not found for user");
            }
            Doctor doctor = doctorOpt.get();
            // Doctors can only see appointments for their patients
            List<AppointmentResponseDto> allAppointments = appointmentService.getAppointmentsByPatientId(id);
            // Filter to only appointments where this doctor is the doctor
            List<AppointmentResponseDto> filtered = allAppointments.stream()
                    .filter(apt -> apt.doctorId().equals(doctor.getId()))
                    .toList();
            return ResponseEntity.ok(filtered);
        }
        
        throw new ForbiddenException("Access denied");
    }

    @Operation(
            summary = "Get appointments by doctor ID",
            description = "Get appointments for a specific doctor. Requires authentication.",
            parameters = @Parameter(name = "id", description = "Doctor ID", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "List of appointments for the doctor retrieved successfully.",
                    content = @Content(
                            mediaType = "application/json",
                            array = @ArraySchema(schema = @Schema(implementation = AppointmentResponseDto.class))
                    )
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "Unauthorized - Missing or expired JWT"
            )
    })
    @GetMapping("/doctor/{id}")
    public ResponseEntity<List<AppointmentResponseDto>> getAppointmentsByDoctorId(
            Authentication authentication,
            @PathVariable UUID id) {
        String userEmail = authentication.getName();
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));
        
        // Check if user is the doctor or a patient viewing their appointments with this doctor
        if (user.getRole().name().equals("DOCTOR")) {
            Optional<Doctor> doctorOpt = doctorRepository.findByUser(user);
            if (doctorOpt.isEmpty()) {
                throw new ForbiddenException("Doctor profile not found for user");
            }
            Doctor doctor = doctorOpt.get();
            if (!doctor.getId().equals(id)) {
                throw new ForbiddenException("Doctors can only view their own appointments");
            }
            return ResponseEntity.ok(appointmentService.getAppointmentsByDoctorId(id));
        } else if (user.getRole().name().equals("PATIENT")) {
            Optional<Patient> patientOpt = patientRepository.findByUser(user);
            if (patientOpt.isEmpty()) {
                throw new ForbiddenException("Patient profile not found for user");
            }
            Patient patient = patientOpt.get();
            // Patients can see appointments with a specific doctor (for viewing doctor's schedule)
            List<AppointmentResponseDto> allAppointments = appointmentService.getAppointmentsByDoctorId(id);
            // Filter to only appointments where this patient is the patient
            List<AppointmentResponseDto> filtered = allAppointments.stream()
                    .filter(apt -> apt.patientId().equals(patient.getId()))
                    .toList();
            return ResponseEntity.ok(filtered);
        }
        
        throw new ForbiddenException("Access denied");
    }

    @Operation(
            summary = "Create/Request an appointment",
            description = """
    Schedule a new appointment for patient and doctor. Both doctors and patients can create appointments.
    If the slot is available, the appointment is automatically confirmed. Otherwise, it remains in SCHEDULED status 
    and the doctor can confirm it later. Email notifications are sent automatically. 
    The system automatically checks for availability and conflicts.""",
            requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
                    description = "Appointment details",
                    required = true,
                    content = @Content(schema = @Schema(implementation = AppointmentRequestDto.class))
            )
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Appointment created successfully.",
                    content = @Content(
                            mediaType = "application/json",
                            schema = @Schema(implementation = AppointmentResponseDto.class)
                    )
            ),
            @ApiResponse(
                    responseCode = "400",
                    description = "Validation error"
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Forbidden - Only doctors and patients can create appointments."
            )
    })
    @PostMapping
    public ResponseEntity<AppointmentResponseDto> createAppointment(@RequestBody @Valid AppointmentRequestDto appointmentRequest) {
        // Sanitize UUID inputs
        if (appointmentRequest.patientId() != null) {
            String sanitized = InputSanitizer.sanitizeUuid(appointmentRequest.patientId().toString());
            if (sanitized == null) {
                log.warn("Invalid patient ID in appointment request");
                throw new IllegalArgumentException("Invalid patient ID format");
            }
        }
        if (appointmentRequest.doctorId() != null) {
            String sanitized = InputSanitizer.sanitizeUuid(appointmentRequest.doctorId().toString());
            if (sanitized == null) {
                log.warn("Invalid doctor ID in appointment request");
                throw new IllegalArgumentException("Invalid doctor ID format");
            }
        }
        
        return ResponseEntity.ok(appointmentService.createAppointment(appointmentRequest));
    }

    @Operation(
            summary = "Cancel an appointment",
            description = "Change appointment status to CANCELLED.",
            parameters = @Parameter(name = "id", description = "Appointment ID", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Appointment cancelled successfully",
                    content = @Content(schema = @Schema(implementation = AppointmentResponseDto.class))
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "Unauthorized - Missing or expired JWT"
            )
    })
    @PatchMapping("/{id}/cancel")
    public ResponseEntity<AppointmentResponseDto> cancelAppointment(@PathVariable UUID id) {
        return ResponseEntity.ok(appointmentService.changeAppointmentStatus(id, AppointmentStatus.CANCELLED));
    }

    @Operation(
            summary = "Confirm an appointment",
            description = "Change appointment status to CONFIRMED. Only doctors can confirm appointments. Patients can only request appointments, which are auto-confirmed if the slot is available.",
            parameters = @Parameter(name = "id", description = "Appointment ID", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Appointment confirmed successfully",
                    content = @Content(schema = @Schema(implementation = AppointmentResponseDto.class))
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "Unauthorized - Missing or expired JWT"
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Forbidden - Only doctors can confirm appointments"
            )
    })
    @PatchMapping("/{id}/confirm")
    public ResponseEntity<AppointmentResponseDto> confirmAppointment(
            Authentication authentication,
            @PathVariable UUID id) {
        String userEmail = authentication.getName();
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));
        
        // Only doctors can confirm appointments
        if (!user.getRole().name().equals("DOCTOR")) {
            throw new ForbiddenException("Access denied");
        }
        
        // Verify the appointment belongs to this doctor
        AppointmentResponseDto appointment = appointmentService.getAppointmentById(id);
        Optional<Doctor> doctorOpt = doctorRepository.findByUser(user);
        if (doctorOpt.isEmpty()) {
            throw new ForbiddenException("Access denied");
        }
        Doctor doctor = doctorOpt.get();
        
        // Check if this appointment belongs to the authenticated doctor
        if (!appointment.doctorId().equals(doctor.getId())) {
            throw new ForbiddenException("Access denied"); // Doctors can only confirm their own appointments
        }
        
        return ResponseEntity.ok(appointmentService.changeAppointmentStatus(id, AppointmentStatus.CONFIRMED));
    }

    @Operation(
            summary = "Complete an appointment",
            description = "Change appointment status to COMPLETED.",
            parameters = @Parameter(name = "id", description = "Appointment ID", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Appointment completed successfully",
                    content = @Content(schema = @Schema(implementation = AppointmentResponseDto.class))
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "Unauthorized - Missing or expired JWT"
            )
    })
    @PatchMapping("/{id}/complete")
    public ResponseEntity<AppointmentResponseDto> completeAppointment(@PathVariable UUID id) {
        return ResponseEntity.ok(appointmentService.changeAppointmentStatus(id, AppointmentStatus.COMPLETED));
    }

    @Operation(
            summary = "Delete an appointment",
            description = "Delete an appointment by ID. Patients and doctors can delete their own appointments. Admins can delete any appointment.",
            parameters = @Parameter(name = "id", description = "Appointment ID", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "204",
                    description = "Appointment deleted successfully"
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Forbidden - You can only delete your own appointments"
            )
    })
    @DeleteMapping("/{id}")
    @Transactional
    public ResponseEntity<Void> deleteAppointment(
            Authentication authentication,
            @PathVariable UUID id) {
        String userEmail = authentication.getName();
        log.info("Delete appointment request - User: {}, Appointment ID: {}", userEmail, id);
        
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new RuntimeException("User not found"));
        
        // Get appointment entity directly to check relationships
        Appointment appointment = appointmentService.getAppointmentEntityById(id);
        log.info("Appointment loaded - Patient: {}, Doctor: {}", 
            appointment.getPatient() != null ? appointment.getPatient().getId() : "null",
            appointment.getDoctor() != null ? appointment.getDoctor().getId() : "null");
        
        // Check if user is the patient or the doctor, or an admin
        if (user.getRole().name().equals("ADMIN")) {
            // Admins can hard delete any appointment
            appointmentService.deleteAppointment(id);
            return ResponseEntity.noContent().build();
        } else if (user.getRole().name().equals("PATIENT")) {
            Optional<Patient> patientOpt = patientRepository.findByUser(user);
            if (patientOpt.isEmpty()) {
                log.warn("Patient record not found for user: {}", userEmail);
                throw new ForbiddenException("Access denied");
            }
            Patient patient = patientOpt.get();
            log.info("Current patient ID: {}", patient.getId());
            
            // Patients can only delete their own appointments (soft delete)
            if (appointment.getPatient() == null) {
                log.error("Appointment {} has null patient relationship", id);
                throw new ForbiddenException("Access denied");
            }
            
            UUID appointmentPatientId = appointment.getPatient().getId();
            UUID currentPatientId = patient.getId();
            log.info("Comparing - Appointment patient ID: {}, Current patient ID: {}, Match: {}", 
                appointmentPatientId, currentPatientId, appointmentPatientId.equals(currentPatientId));
            
            if (!appointmentPatientId.equals(currentPatientId)) {
                log.error("Authorization failed - Patient {} attempted to delete appointment {} which belongs to patient {}", 
                    currentPatientId, id, appointmentPatientId);
                throw new ForbiddenException("Access denied");
            }
            
            log.info("Authorization successful - deleting appointment {} for patient {}", id, currentPatientId);
            appointmentService.softDeleteAppointmentByPatient(id);
            return ResponseEntity.noContent().build();
        } else if (user.getRole().name().equals("DOCTOR")) {
            Optional<Doctor> doctorOpt = doctorRepository.findByUser(user);
            if (doctorOpt.isEmpty()) {
                throw new ForbiddenException("Access denied");
            }
            Doctor doctor = doctorOpt.get();
            // Doctors can only delete their own appointments (soft delete)
            if (appointment.getDoctor() == null || !appointment.getDoctor().getId().equals(doctor.getId())) {
                log.warn("Doctor {} attempted to delete appointment {} which belongs to doctor {}", 
                    doctor.getId(), id, appointment.getDoctor() != null ? appointment.getDoctor().getId() : "null");
                throw new ForbiddenException("Access denied");
            }
            appointmentService.softDeleteAppointmentByDoctor(id);
            return ResponseEntity.noContent().build();
        }
        
        throw new ForbiddenException("Access denied");
    }

    @Operation(
            summary = "Get available time slots for a doctor on a specific date",
            description = "Get a list of available time slots for a doctor on a specific date based on doctor's duration preference. Takes into account doctor's availability schedule and existing appointments.",
            parameters = {
                    @Parameter(name = "doctorId", description = "Doctor ID", required = true),
                    @Parameter(name = "date", description = "Date in YYYY-MM-DD format", required = true),
                    @Parameter(name = "durationMinutes", description = "Duration in minutes (optional, uses doctor's default if not provided)", required = false)
            }
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Available time slots retrieved successfully",
                    content = @Content(
                            mediaType = "application/json",
                            array = @ArraySchema(schema = @Schema(implementation = String.class))
                    )
            ),
            @ApiResponse(
                    responseCode = "401",
                    description = "Unauthorized - Missing or expired JWT"
            )
    })
    @GetMapping("/available-slots/{doctorId}/{date}")
    public ResponseEntity<List<String>> getAvailableTimeSlots(
            @PathVariable UUID doctorId,
            @PathVariable String date,
            @RequestParam(required = false) Integer durationMinutes) {
        return ResponseEntity.ok(appointmentService.getAvailableTimeSlots(doctorId, java.time.LocalDate.parse(date), durationMinutes));
    }
    
    @Operation(
            summary = "Remove patient from doctor's history",
            description = "Soft-delete all appointments between a doctor and patient. This removes the patient from the doctor's view. Only the doctor can perform this action.",
            parameters = @Parameter(name = "patientId", description = "Patient ID to remove from history", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Patient removed from history successfully"
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Forbidden - Only doctors can remove patients from their history"
            )
    })
    @PostMapping("/doctor/remove-patient/{patientId}")
    @Transactional
    public ResponseEntity<Void> removePatientFromHistory(
            Authentication authentication,
            @PathVariable UUID patientId) {
        String userEmail = authentication.getName();
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));
        
        if (!user.getRole().name().equals("DOCTOR")) {
            throw new ForbiddenException("Access denied");
        }
        
        Optional<Doctor> doctorOpt = doctorRepository.findByUser(user);
        if (doctorOpt.isEmpty()) {
            throw new ForbiddenException("Access denied");
        }
        
        Doctor doctor = doctorOpt.get();
        appointmentService.removePatientFromDoctorHistory(doctor.getId(), patientId);
        log.info("Doctor {} removed patient {} from history", doctor.getId(), patientId);
        return ResponseEntity.ok().build();
    }
    
    @Operation(
            summary = "Remove doctor from patient's history",
            description = "Soft-delete all appointments between a patient and doctor. This removes the doctor from the patient's view. Only the patient can perform this action.",
            parameters = @Parameter(name = "doctorId", description = "Doctor ID to remove from history", required = true)
    )
    @ApiResponses(value = {
            @ApiResponse(
                    responseCode = "200",
                    description = "Doctor removed from history successfully"
            ),
            @ApiResponse(
                    responseCode = "403",
                    description = "Forbidden - Only patients can remove doctors from their history"
            )
    })
    @PostMapping("/patient/remove-doctor/{doctorId}")
    @Transactional
    public ResponseEntity<Void> removeDoctorFromHistory(
            Authentication authentication,
            @PathVariable UUID doctorId) {
        String userEmail = authentication.getName();
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));
        
        if (!user.getRole().name().equals("PATIENT")) {
            throw new ForbiddenException("Access denied");
        }
        
        Optional<Patient> patientOpt = patientRepository.findByUser(user);
        if (patientOpt.isEmpty()) {
            throw new ForbiddenException("Access denied");
        }
        
        Patient patient = patientOpt.get();
        appointmentService.removeDoctorFromPatientHistory(doctorId, patient.getId());
        log.info("Patient {} removed doctor {} from history", patient.getId(), doctorId);
        return ResponseEntity.ok().build();
    }
}
