<?php

namespace App\Http\Controllers\Admin\Api;

use Agence104\LiveKit\VideoGrant;
use App\Http\Controllers\Controller;
use App\Models\Appointment;
use App\Models\Cart;
use App\Models\LabkitOrderItem;
use App\Models\Patient;
use App\Models\PatientRegActivity;
use App\Models\Setting;
use App\Models\Telemedpro;
use Carbon\Carbon;
use Carbon\CarbonTimeZone;
use DateTime;
use DateTimeZone;
use Error;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Yajra\DataTables\DataTables;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Agence104\LiveKit\AccessToken;
use Agence104\LiveKit\AccessTokenOptions;
use Agence104\LiveKit\RoomCreateOptions;
use Agence104\LiveKit\RoomServiceClient;
use Google\Protobuf\TwirpError;
use Illuminate\Auth\Access\AuthorizationException;

class AppointmentController extends Controller
{
    protected $url;
    protected $user;
    public function __construct(UrlGenerator $url)
    {
        $this->url = $url;
        $this->user = Auth::guard('admin')->user();
    }
    public function getAppointmentList()
    {
        try {
            $this->authorizeForUser($this->user, 'list', new Appointment);
            $appointments = Appointment::select("patients.first_name", "patients.last_name", "telemed_pros.name as agent_name", "appointments.*") // Eager load the associated telemed pro
                ->leftJoin("telemed_pros", "telemed_pros.id", "appointments.telemed_pros_id")
                ->leftJoin("patients", "patients.id", "appointments.patient_id")
                /* ->orderBy('appointment_time', 'desc') */ // Optional: sort by appointment time
                ->get();

            return response()->json($appointments, 200);
        } catch (AuthorizationException $e) {

            return response()->json(['error' => 'Failed to retrieve appointments'], 500);
        }
    }
    public function getMeetingHistory(Patient $patient, $filter = '12_months')
    {
        try {
            $this->authorizeForUser($this->user, 'meeting_history', new Appointment);
            $currentMonth = Carbon::now();

            // Filter logic
            switch ($filter) {
                case 'current_month':
                    $startDate = $currentMonth->copy()->startOfMonth();
                    break;
                case '1_month':
                    $startDate = $currentMonth->copy()->subMonth()->startOfMonth();
                    break;
                case '2_months':
                    $startDate = $currentMonth->copy()->subMonths(2)->startOfMonth();
                    break;
                case '3_months':
                    $startDate = $currentMonth->copy()->subMonths(3)->startOfMonth();
                    break;
                case '6_months':
                    $startDate = $currentMonth->copy()->subMonths(6)->startOfMonth();
                    break;
                default: // Default to 12 months
                    $startDate = $currentMonth->copy()->subMonths(12)->startOfMonth();
            }

            $endDate = $currentMonth->endOfMonth();

            // Fetch patient names and appointment counts directly from the database
            $monthlyData = Appointment::select(
                'patient_id',
                /* DB::raw('COUNT(*) as appointment_count'), */
                'appointment_time',
                'appointment_date',
                'start_time',
                'end_time',
                'duration',
                'id'
            )
                ->where("patient_id", $patient->id)
                ->whereNotNull("end_time")
                ->whereBetween('created_at', [$startDate, $endDate])

                ->get();

            $patients = [];

            foreach ($monthlyData as $dataPoint) {
                $patientName = $dataPoint->patient->first_name . " " . $dataPoint->patient->last_name; // Assuming 'name' is the field representing patient names
                /* $appointmentCount = $dataPoint->appointment_count; */
                $start_time = $dataPoint->start_time;
                $end_time = $dataPoint->end_time;
                $duration = $dataPoint->duration;
                $appointment_time = $dataPoint->appointment_time;
                $appointment_date = $dataPoint->appointment_date;
                $id = $dataPoint->id;

                $patients[] = [
                    'patient_name' => $patientName,
                    'appointment_time' => $appointment_time,
                    'appointment_date' => $appointment_date,
                    /* 'appointment_count' => $appointmentCount, */
                    'start_time' => $start_time,
                    'end_time' => $end_time,
                    'duration' => $duration,
                    'id' => $id,
                ];
            }

            return response()->json([
                'patients' => $patients,
            ]);
        } catch (AuthorizationException $e) {

            return response()->json(['error' => 'Failed to retrieve appointments'], 500);
        }
    }
    public function getAppointmentByid($patient, $appointment, Request $request)
    {
        try {
            $this->authorizeForUser($this->user, 'list', new Appointment);
            // Assuming user can be either telemedPro or patient
            $data = Appointment::select('appointments.*', 'telemed_pros.name as agent_name')
                ->leftJoin('telemed_pros', 'appointments.telemed_pros_id', '=', 'telemed_pros.id')
                ->where('appointments.patient_id', $patient)
                ->where('appointments.id', $appointment)
                ->first();
            // dd($data);
            return response()->json(['data' => $data]);
        } catch (AuthorizationException $e) {

            return response()->json(['error' => 'Failed to retrieve appointments'], 500);
        }
    }
    public function bookAppointment(Request $request)
    {
        try {
            $this->authorizeForUser($this->user, 'list', new Appointment);
            $validatedData = $request->validate([
                /* 'telemed_pros_id' => 'required|exists:telemed_pros,id', */
                'patient_id' => 'required|exists:patients,id',
                'appointment_time' => 'required|date_format:H:i:s',
                'appointment_date' => 'required|date_format:Y-m-d',
                'patient_name' => 'required',
                'patient_email' => 'required',
                'timezone' => 'required',
            ]);
            try {
                $tz = new DateTimeZone($validatedData['timezone']);
                $standardTz = $tz->getName();
            } catch (Exception $e) {
                return response()->json([
                    'message' => $e->getMessage()
                ], 400);
            }
            try {
                $timezoneMap = [
                    'EST' => 'America/New_York',
                    'EDT' => 'America/New_York',
                    'CST' => 'America/Chicago',
                    'CDT' => 'America/Chicago',
                    'MST' => 'America/Denver',
                    'MDT' => 'America/Denver',
                    'PST' => 'America/Los_Angeles',
                    'PDT' => 'America/Los_Angeles',
                    // Add more mappings as needed
                ];
                $timezone = $validatedData['timezone'];
                if (array_key_exists($timezone, $timezoneMap)) {
                    $timezone = $timezoneMap[$timezone];
                }

                $appointmentDateTime = new DateTime(
                    $validatedData['appointment_date'] . ' ' . $validatedData['appointment_time'],
                    new DateTimeZone($timezone)
                );

                $appointmentDateTime->setTimezone(new DateTimeZone('UTC'));

                $validatedData['appointment_time'] = $appointmentDateTime->format('H:i:s');
                $validatedData['appointment_date'] = $appointmentDateTime->format('Y-m-d');
            } catch (Exception $e) {
                return response()->json([
                    'message' => $e->getMessage()
                ], 400);
            }

            $availableTelemedPros = Telemedpro::select("telemed_pros.id", "telemed_pros.name")/* ->where('is_busy', false) */
                ->leftJoin('appointments', function ($join) use ($validatedData) {
                    $join->on('telemed_pros.id', '=', 'appointments.telemed_pros_id')
                        ->where('appointments.appointment_time', '=', $validatedData['appointment_time'])
                        ->where('appointments.appointment_date', '=', $validatedData['appointment_date']);
                })
                ->whereNull('appointments.id')
                ->first();

            if (!$availableTelemedPros)
                return response()->json([
                    'message' => 'Appointment time not available'
                ], 400);

            $existingAppointment = Appointment::where('telemed_pros_id', $availableTelemedPros->id)
                ->where('appointment_time', $validatedData['appointment_time'])
                ->where('appointment_date', $validatedData['appointment_date'])
                ->first();

            if ($existingAppointment) {
                return response()->json([
                    'message' => 'Appointment time not available'
                ], 400);
            }
            $validatedData['telemed_pros_id'] = $availableTelemedPros->id;
            $validatedData['status'] = 'pending';

            // Create the appointment
            $appointment = Appointment::create($validatedData);
            $appointment_booking_tokens = $this->bookAppointmentApi($appointment, $availableTelemedPros);
            $appointment->agent_call_token = $appointment_booking_tokens['tokenAgent'];
            $appointment->patient_call_token = $appointment_booking_tokens['tokenPatient'];
            $appointment->save();

            PatientRegActivity::create([
                'patient_id' => $validatedData['patient_id'],
                'activity' => 'patient_appointment_booked'
            ]);
            $patient = $appointment->patient;
            $datetimeUtc = $appointment->appointment_date . ' ' . $appointment->appointment_time;
            $dateTimeUtc = Carbon::createFromFormat('Y-m-d H:i:s', $datetimeUtc, 'UTC');
            $appointmentTimeZone = new CarbonTimeZone($appointment->timezone);
            $dateTimeInAppointmentTimeZone = $dateTimeUtc->setTimezone($appointmentTimeZone);
            $appointment->appointment_date = $appointmentDate = $dateTimeInAppointmentTimeZone->format('Y-m-d');
            $appointment->appointment_time = $appointmentTime = $dateTimeInAppointmentTimeZone->format('H:i:s');
            $setting = Setting::find(1);
            //event(new AppointmentBooked($appointment));
            $cart = Cart::find($request->input("cart_id"));
            $cart->appointment_id = $appointment->id;
            $cart->save();
            return response()->json([
                'message' => 'Appointment booked successfully',
                'meeting_id' => $appointment->agent_call_token,
                'appointment' => $appointment,
                'appointment_time' => $validatedData['appointment_time'],
                'appointment_date' => $validatedData['appointment_date']
            ]);
        } catch (AuthorizationException $e) {

            return response()->json(['error' => $e->getMessage()], 500);
        }
    }
    public function editAppointment(Appointment $appointment, Request $request)
    {
        try {
            $this->authorizeForUser($this->user, 'list', new Appointment);
            $validatedData = $request->validate([
                'patient_id' => 'sometimes|exists:patients,id',
                'appointment_time' => 'sometimes|date_format:H:i:s',
                'appointment_date' => 'sometimes|date_format:Y-m-d',
                'patient_name' => 'sometimes|string',
                'patient_email' => 'sometimes|email',
                'timezone' => 'sometimes|string',
            ]);

            if (isset($validatedData['timezone'])) {
                try {
                    $tz = new DateTimeZone($validatedData['timezone']);
                    $standardTz = $tz->getName();
                } catch (Exception $e) {
                    return response()->json([
                        'message' => $e->getMessage()
                    ], 400);
                }

                $timezoneMap = [
                    'EST' => 'America/New_York',
                    'EDT' => 'America/New_York',
                    'CST' => 'America/Chicago',
                    'CDT' => 'America/Chicago',
                    'MST' => 'America/Denver',
                    'MDT' => 'America/Denver',
                    'PST' => 'America/Los_Angeles',
                    'PDT' => 'America/Los_Angeles',
                    // Add more mappings as needed
                ];

                $timezone = $validatedData['timezone'];
                if (array_key_exists($timezone, $timezoneMap)) {
                    $timezone = $timezoneMap[$timezone];
                }

                if (isset($validatedData['appointment_date']) && isset($validatedData['appointment_time'])) {
                    try {
                        $appointmentDateTime = new DateTime(
                            $validatedData['appointment_date'] . ' ' . $validatedData['appointment_time'],
                            new DateTimeZone($timezone)
                        );

                        $appointmentDateTime->setTimezone(new DateTimeZone('UTC'));

                        $validatedData['appointment_time'] = $appointmentDateTime->format('H:i:s');
                        $validatedData['appointment_date'] = $appointmentDateTime->format('Y-m-d');
                    } catch (Exception $e) {
                        return response()->json([
                            'message' => $e->getMessage()
                        ], 400);
                    }

                    // Check if the new time slot is available
                    $availableTelemedPros = Telemedpro::select("telemed_pros.id", "telemed_pros.name")
                        ->leftJoin('appointments', function ($join) use ($validatedData, $appointment) {
                            $join->on('telemed_pros.id', '=', 'appointments.telemed_pros_id')
                                ->where('appointments.appointment_time', '=', $validatedData['appointment_time'])
                                ->where('appointments.appointment_date', '=', $validatedData['appointment_date'])
                                ->where('appointments.id', '!=', $appointment->id); // Exclude the current appointment
                        })
                        ->whereNull('appointments.id')
                        ->first();

                    if (!$availableTelemedPros) {
                        return response()->json([
                            'message' => 'New appointment time not available'
                        ], 400);
                    }

                    // Update the telemed_pros_id if it's different
                    if ($availableTelemedPros->id !== $appointment->telemed_pros_id) {
                        $validatedData['telemed_pros_id'] = $availableTelemedPros->id;

                        // Re-book the appointment with the new telemed pro
                        $appointment_booking_tokens = $this->bookAppointmentApi($appointment, $availableTelemedPros);
                        $validatedData['agent_call_token'] = $appointment_booking_tokens['tokenAgent'];
                        $validatedData['patient_call_token'] = $appointment_booking_tokens['tokenPatient'];
                    }
                }

                // Update the appointment
                $appointment->update($validatedData);

                // Update related cart if it exists
                $cart = Cart::where('appointment_id', $appointment->id)->first();
                if ($cart) {
                    $cart->appointment_id = $appointment->id;
                    $cart->save();
                }

                // Convert appointment time to the specified timezone for the response
                $datetimeUtc = $appointment->appointment_date . ' ' . $appointment->appointment_time;
                $dateTimeUtc = Carbon::createFromFormat('Y-m-d H:i:s', $datetimeUtc, 'UTC');
                $appointmentTimeZone = new CarbonTimeZone($appointment->timezone);
                $dateTimeInAppointmentTimeZone = $dateTimeUtc->setTimezone($appointmentTimeZone);
                $appointmentDate = $dateTimeInAppointmentTimeZone->format('Y-m-d');
                $appointmentTime = $dateTimeInAppointmentTimeZone->format('H:i:s');

                return response()->json([
                    'message' => 'Appointment updated successfully',
                    'meeting_id' => $appointment->agent_call_token,
                    'appointment' => $appointment,
                    'appointment_time' => $appointmentTime,
                    'appointment_date' => $appointmentDate
                ]);
            }
        } catch (AuthorizationException $e) {
            return response()->json(['error' => $e->getMessage()], 500);
        }
    }
    public function bookAppointmentApi($appointment, $availableTelemedPros)
    {

        try {
            $this->authorizeForUser($this->user, 'edit', new Appointment);
            $roomName = 'appointment-' . $appointment->id . "-" . uniqid();
            $opts = (new RoomCreateOptions())
                ->setName($roomName)
                ->setEmptyTimeout(10)
                ->setMaxParticipants(5);
            $host = "https://plugnmeet.codelfi.com";
            $svc = new RoomServiceClient($host, config('app.LK_API_KEY'), config('app.LK_API_SECRET'));
            $room = $svc->createRoom($opts);

            $participantPatientName = "patient-" . uniqid() . $appointment->patient->first_name . " " . $appointment->patient->last_name;

            $tokenOptionsPatient = (new AccessTokenOptions())
                ->setIdentity($participantPatientName);
            $videoGrantPatient = (new VideoGrant())
                ->setRoomJoin()
                ->setRoomName($roomName);
            $tokenPatient = (new AccessToken(config('app.LK_API_KEY'), config('app.LK_API_SECRET')))
                ->init($tokenOptionsPatient)
                ->setGrant($videoGrantPatient)
                ->toJwt();

            $participantAgentName = "agent-" . uniqid() . $availableTelemedPros->name;
            $tokenOptionsAgent = (new AccessTokenOptions())
                ->setIdentity($participantAgentName);
            $videoGrantAgent = (new VideoGrant())
                ->setRoomJoin()
                ->setRoomName($roomName);
            $tokenAgent = (new AccessToken(config('app.LK_API_KEY'), config('app.LK_API_SECRET')))
                ->init($tokenOptionsAgent)
                ->setGrant($videoGrantAgent)
                ->toJwt();
            return [
                'tokenPatient' => $tokenPatient,
                'tokenAgent' => $tokenAgent,
            ];
        } catch (AuthorizationException | Error | Exception | TwirpError $e) {

            return response()->json(['error' => $e->getMessage()], 500);
        }
    }

    public function availableSlots($date)
    {
        try {
            $this->authorizeForUser($this->user, 'book_appointment', new Appointment);
            // Ensure date is in a valid format
            $date = Carbon::parse($date);
            $originalDate = Carbon::parse($date);

            // Generate all possible 30-minute slots between 9 AM and 4 PM
            $slots = collect();
            $startTime = Carbon::parse($date)->subHours(24)->setTime(9, 0, 0);
            $endTime = Carbon::parse($date)->addHours(24)->setTime(16, 0, 0);
            while ($startTime < $endTime) {
                $slots->push($startTime->format('Y-m-d H:i:s'));
                $startTime->addMinutes(15);
            }

            /* $user = Patient::find($patient_id); */
            // Filter out booked slots
            $bookedAppointments = Appointment::where('appointment_date', '>=', $date->format('Y-m-d'))
                ->where('appointment_date', '<', $date->addDay()->format('Y-m-d'))
                ->pluck('appointment_date');

            $availableSlots = $slots->diff($bookedAppointments);

            $formattedSlots = $availableSlots->map(function ($slot) {
                $start = Carbon::parse($slot);
                $startTime = $start->format('Y-m-d H:i:s');
                return $startTime;
            });

            // Additional checking if slot is booked
            $formattedSlots = $formattedSlots->filter(function ($slot) use ($originalDate) {
                $time = Carbon::parse($slot);
                return !Appointment::where('appointment_time', $time->format('H:i:s'))
                    ->where('appointment_date', $originalDate->format('Y-m-d'))
                    ->exists();
            });

            return response()->json([
                'available_slots' => $formattedSlots->toArray()
            ]);
        } catch (AuthorizationException $e) {

            return response()->json(['error' => $e->getMessage()], 500);
        }
    }
    public function getItemByOrder($order_id)
    {
        try {
            $this->authorizeForUser($this->user, 'list', new Appointment);
            $labkits = LabkitOrderItem::leftJoin(
                'lab_kit',
                'labkit_order_items.lab_kit_id',
                'lab_kit.id'
            )
                ->leftJoin(
                    'items',
                    'items.id',
                    'labkit_order_items.item_id'
                )
                ->leftJoin(
                    'plans_v1',
                    'plans_v1.id',
                    'items.plans_id'
                )
                ->leftJoin(
                    'carts',
                    'carts.id',
                    'labkit_order_items.cart_id'
                )
                ->where('carts.id', $order_id)
                ->select(
                    'labkit_order_items.id',
                    'labkit_order_items.status',
                    'labkit_order_items.result',
                    'lab_kit.name as lab_kit_name',
                    'plans_v1.id as product_id',
                    'plans_v1.title as product_name'
                )
                ->get();
            return response()->json([
                'order_item' => $labkits
            ]);
        } catch (AuthorizationException $e) {

            return response()->json(['error' => $e->getMessage()], 500);
        }
    }
    public function getAgentLastAppointment(Patient $patient, Request $request)
    {
        try {
            $this->authorizeForUser($this->user, 'list', new Appointment);
            $appointments = Appointment::select(
                "patients.first_name",
                "patients.last_name",
                "telemed_pros.name as agent_name",
                "appointments.*",
                "carts.shipping_address1",
                "carts.shipping_address2",
                "carts.id as order_id",
                "carts.shipping_city",
                "carts.shipping_state",
                "carts.shipping_zipcode",
                "carts.shipping_country"

            ) // Eager load the associated telemed pro
                ->leftJoin("telemed_pros", "telemed_pros.id", "appointments.telemed_pros_id")
                ->leftJoin("patients", "patients.id", "appointments.patient_id")
                ->leftJoin("carts", "carts.appointment_id", "appointments.id")
                ->where("appointments.patient_id", $patient->id)
                ->orderBy('appointments.created_at', 'desc')
                ->first();

            $upcoming_appointments = Appointment::select(
                "patients.first_name",
                "patients.last_name",
                "telemed_pros.name as agent_name",
                "appointments.*",
                "carts.shipping_address1",
                "carts.shipping_address2",
                "carts.id as order_id",
                "carts.shipping_city",
                "carts.shipping_state",
                "carts.shipping_zipcode",
                "carts.shipping_country",
                "appointments.id as order_appointment_id"

            ) // Eager load the associated telemed pro
                ->leftJoin("telemed_pros", "telemed_pros.id", "appointments.telemed_pros_id")
                ->leftJoin("patients", "patients.id", "appointments.patient_id")
                ->leftJoin("carts", "carts.appointment_id", "appointments.id")
                //->where('appointments.appointment_date', '<', $appointments->appointment_date)
                ->where("appointments.patient_id", $patient->id)
                ->where("appointments.status", 'pending')
                ->whereNull("appointments.start_time")

                ->orderBy('appointments.created_at', 'desc')
                ->get();

            if (!$appointments)
                return response()->json(['error' => 'No Record found.'], 500);

            $timezone = config('app.timezone');
            if ($appointments->timezone) {

                $tz = new DateTimeZone($appointments->timezone);

                $standardTz = $tz->getName();
                $appointmentDateTime = $appointmentCurrent = Carbon::parse($appointments->appointment_date . ' ' . $appointments->appointment_time)->shiftTimezone($standardTz);
                //$appointmentDateTime =  $appointmentDateTime->shiftTimezone($timezone);

                $appointmentCurrent = Carbon::now($timezone);

                $diff = $appointmentDateTime->diff($appointmentCurrent);

                if ($diff->invert == 0) {
                    // Appointment is in future, increment count
                    $diff = $diff->format('0 days 0 hours 0 minutes 0 seconds');
                } else

                    $diff = $diff->format('%a days %h hours %i minutes %s seconds');
            } else {
                $diff = "";
            }
            $filePath = public_path("assets/profiles/{$patient->id}.png");

            if ($patient->profile_picture)
                $patient->profile_picture = $this->url->to("storage/profile_pictures", $patient->profile_picture);
            else
                $patient->profile_picture = null;

            if (File::exists($filePath)) {
                $patient->url = "/assets/profiles/{$patient->id}.png";
            } else {
                $patient->url = null;
            }
            foreach ($upcoming_appointments as $upcoming_appointment) {

                if ($upcoming_appointment->timezone) {

                    $tz = new DateTimeZone($upcoming_appointment->timezone);

                    $standardTz = $tz->getName();
                    $appointmentDateTime = $appointmentCurrent = Carbon::parse($upcoming_appointment->appointment_date . ' ' . $upcoming_appointment->appointment_time)->shiftTimezone($standardTz);
                    //$appointmentDateTime =  $appointmentDateTime->shiftTimezone($timezone);

                    $appointmentCurrent = Carbon::now($timezone);

                    $diff = $appointmentDateTime->diff($appointmentCurrent);

                    if ($diff->invert == 0) {
                        // Appointment is in future, increment count
                        $diff = $diff->format('0 days 0 hours 0 minutes 0 seconds');
                    } else

                        $diff = $diff->format('%a days %h hours %i minutes %s seconds');
                } else {
                    $diff = "";
                }

                $upcoming_appointment->items_data = $this->getOrderItems($upcoming_appointment->order_id);
                $upcoming_appointment->time_diff = $diff;
            }

            return response()->json(['upcoming_appointments' => $upcoming_appointments, 'appointment' => $appointments, 'time_diff' => $diff, 'patient' => $patient, "items_data" => $this->getOrderItems($appointments->order_id)], 200);
        } catch (\Exception $e) {
            return response()->json(['error' => 'Failed to retrieve appointments'], 500);
        }
    }
}
