import Appointments from '../../appointments/schema/appointments.schema';
import PaymentLink from '../schema/payment-link.schema';
import { pesewasToGhs, verifyTransaction } from '../../../helpers/paystack.helper';

function resolvePaymentStatus(amountPaid: number, amountDue: number) {
  if (amountPaid >= amountDue && amountDue > 0) return 'PAID';
  if (amountPaid > 0 && amountDue > 0) return 'PARTIAL';
  if (amountPaid > 0) return 'PAID';
  return 'UNPAID';
}

async function applyPaymentToAppointment(
  appointment: InstanceType<typeof Appointments>,
  link: InstanceType<typeof PaymentLink>,
  paidAmountGhs: number
) {
  const amountDue = appointment.amountDue || link.amount || paidAmountGhs;
  const amountPaid = paidAmountGhs || link.amount || amountDue;

  appointment.amountPaid = amountPaid;
  appointment.amountDue = amountDue;
  appointment.paymentMethod = 'LINK';
  appointment.paymentStatus = resolvePaymentStatus(amountPaid, amountDue);
  appointment.paymentLinkId = link._id.toString();
  await appointment.save();

  if (link.status !== 'PAID') {
    link.status = 'PAID';
    await link.save();
  }

  return appointment;
}

export async function settlePaymentByReference(reference: string, paidAmountGhs: number) {
  const link = await PaymentLink.findOne({ reference });
  if (!link) {
    return { settled: false, reason: 'Payment link not found' };
  }

  if (!link.appointmentId) {
    if (link.status !== 'PAID') {
      link.status = 'PAID';
      await link.save();
    }
    return { settled: true, reason: 'Template link marked paid', link };
  }

  const appointment = await Appointments.findById(link.appointmentId);
  if (!appointment) {
    return { settled: false, reason: 'Appointment not found', link };
  }

  if (link.status === 'PAID' && appointment.paymentStatus === 'PAID') {
    return { settled: true, reason: 'Already settled', link, appointment };
  }

  const updated = await applyPaymentToAppointment(appointment, link, paidAmountGhs);
  return { settled: true, reason: 'Appointment updated', link, appointment: updated };
}

export function paidAmountFromVerification(data: { amount?: number; currency?: string }, fallbackGhs: number) {
  if (typeof data.amount === 'number') {
    return data.currency === 'GHS' ? pesewasToGhs(data.amount) : data.amount;
  }
  return fallbackGhs;
}

const AUTO_VERIFY_STATUSES = new Set(['UNPAID', 'LINK_SENT', 'PARTIAL']);

export async function autoVerifyAppointments<T extends {
  _id: unknown;
  paymentStatus?: string;
  paymentLink?: { reference?: string | null; amount?: number; status?: string } | null;
}>(appointments: T[]) {
  const updates = new Map<string, Awaited<ReturnType<typeof Appointments.findById>>>();

  const candidates = appointments.filter(
    (item) =>
      item.paymentLink?.reference &&
      AUTO_VERIFY_STATUSES.has(item.paymentStatus || 'UNPAID')
  );

  await Promise.all(
    candidates.map(async (item) => {
      try {
        const reference = String(item.paymentLink?.reference);
        const verification = await verifyTransaction(reference);
        if (verification.status !== 'success') return;

        const paidAmount = paidAmountFromVerification(
          verification,
          item.paymentLink?.amount || 0
        );
        const result = await settlePaymentByReference(reference, paidAmount);
        if (result.appointment) {
          updates.set(String(item._id), result.appointment);
        }
      } catch {
        // Ignore Paystack errors during background sync
      }
    })
  );

  return updates;
}

export async function autoVerifySingleAppointment(appointmentId: string) {
  const appointment = await Appointments.findById(appointmentId).lean();
  if (!appointment?.paymentLinkId) return null;

  const paymentLink = await PaymentLink.findById(appointment.paymentLinkId).lean();
  if (!paymentLink?.reference || !AUTO_VERIFY_STATUSES.has(appointment.paymentStatus || 'UNPAID')) {
    return null;
  }

  const updates = await autoVerifyAppointments([
    { ...appointment, paymentLink },
  ]);

  return updates.get(String(appointmentId)) || null;
}
