import { Request, Response } from 'express';
import PaymentLink from '../schema/payment-link.schema';
import Appointments from '../../appointments/schema/appointments.schema';
import { logActivity } from '../../../helpers/activityLog.helper';
import { buildTextSearch, parsePagination, paginationMeta } from '../../../helpers/query.helper';
import {
  PaystackError,
  createPaymentPage,
  initializeTransaction,
  verifyTransaction,
  verifyWebhookSignature,
} from '../../../helpers/paystack.helper';
import {
  paidAmountFromVerification,
  settlePaymentByReference,
} from '../services/payment-settlement.service';

function callbackUrl() {
  const frontend = process.env.FRONTEND_URL || process.env.CLIENT_URL || 'http://localhost:5173';
  return `${frontend.replace(/\/$/, '')}/?payment=success`;
}

function buildReference(prefix: string) {
  return `${prefix}-${Date.now().toString(36).toUpperCase()}`;
}

export class PaymentLinkController {
  static async listPublic(req: Request, res: Response) {
    try {
      const response = await PaymentLink.find({ isActive: true, appointmentId: null }).sort({ createdAt: -1 });
      return res.status(200).json({ success: true, response });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async listAdmin(req: Request, res: Response) {
    try {
      const { page, limit, skip } = parsePagination(req, 10);
      const filter: Record<string, unknown> = { appointmentId: null };

      const active = String(req.query.active || '').trim();
      if (active === 'true') filter.isActive = true;
      if (active === 'false') filter.isActive = false;

      const search = buildTextSearch(
        ['label', 'url', 'serviceCategory', 'notes', 'reference'],
        String(req.query.q || '')
      );
      if (search.$or) Object.assign(filter, search);

      const [response, total] = await Promise.all([
        PaymentLink.find(filter).sort({ createdAt: -1 }).skip(skip).limit(limit),
        PaymentLink.countDocuments(filter),
      ]);

      return res.status(200).json({
        success: true,
        response,
        pagination: paginationMeta(page, limit, total),
      });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async create(req: Request, res: Response) {
    try {
      const { label, amount, serviceCategory, notes, isActive, url } = req.body;

      if (!label || !amount) {
        return res.status(400).json({ success: false, message: 'Label and amount are required' });
      }

      let paymentUrl = String(url || '').trim();
      let reference: string | null = null;
      let paystackId: string | null = null;

      if (!paymentUrl) {
        const page = await createPaymentPage({
          name: label,
          amountGhs: Number(amount),
          description: notes || serviceCategory || label,
        });
        paymentUrl = page.url;
        reference = page.slug;
        paystackId = String(page.id);
      }

      const item = await PaymentLink.create({
        label,
        amount: Number(amount),
        url: paymentUrl,
        reference,
        paystackId,
        serviceCategory: serviceCategory || '',
        notes: notes || '',
        isActive: isActive !== false,
        provider: 'paystack',
        status: 'PENDING',
      });

      await logActivity(req, 'CREATE', 'PAYMENT_LINK', item._id.toString(), label);

      return res.status(201).json({ success: true, response: item });
    } catch (error) {
      if (error instanceof PaystackError) {
        return res.status(error.statusCode).json({ success: false, message: error.message });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async update(req: Request, res: Response) {
    try {
      const item = await PaymentLink.findByIdAndUpdate(req.params.id, req.body, { new: true });
      if (!item) return res.status(404).json({ success: false, message: 'Not found' });
      return res.status(200).json({ success: true, response: item });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async remove(req: Request, res: Response) {
    try {
      await PaymentLink.findByIdAndDelete(req.params.id);
      return res.status(200).json({ success: true, message: 'Deleted' });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async getForAppointment(req: Request, res: Response) {
    try {
      const appointment = await Appointments.findById(req.params.appointmentId);
      if (!appointment) {
        return res.status(404).json({ success: false, message: 'Appointment not found' });
      }

      if (!appointment.paymentLinkId) {
        return res.status(200).json({ success: true, response: null });
      }

      const link = await PaymentLink.findById(appointment.paymentLinkId);
      return res.status(200).json({ success: true, response: link });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async generateForAppointment(req: Request, res: Response) {
    try {
      const { appointmentId, amount, label } = req.body;
      if (!appointmentId) {
        return res.status(400).json({ success: false, message: 'appointmentId required' });
      }

      const appointment = await Appointments.findById(appointmentId);
      if (!appointment) {
        return res.status(404).json({ success: false, message: 'Appointment not found' });
      }

      if (!appointment.email) {
        return res.status(400).json({ success: false, message: 'Appointment email is required for Paystack' });
      }

      const paymentAmount = Number(amount ?? appointment.amountDue);
      if (!paymentAmount || paymentAmount <= 0) {
        return res.status(400).json({
          success: false,
          message: 'Set an amount due on the appointment before generating a payment link',
        });
      }

      const reference = buildReference(`MJL-${appointment._id.toString().slice(-8).toUpperCase()}`);
      const initialized = await initializeTransaction({
        email: appointment.email,
        amountGhs: paymentAmount,
        reference,
        callbackUrl: callbackUrl(),
        metadata: {
          appointmentId: appointment._id.toString(),
          fullName: appointment.fullName,
          service: appointment.service,
        },
      });

      const linkPayload = {
        label: label || `Payment for ${appointment.fullName}`,
        amount: paymentAmount,
        url: initialized.authorization_url,
        reference: initialized.reference,
        accessCode: initialized.access_code,
        appointmentId: appointment._id.toString(),
        provider: 'paystack',
        notes: appointment.service,
        isActive: true,
        status: 'PENDING',
      };

      let link;
      let updated = false;

      if (appointment.paymentLinkId) {
        link = await PaymentLink.findByIdAndUpdate(appointment.paymentLinkId, linkPayload, { new: true });
        updated = true;
      }

      if (!link) {
        link = await PaymentLink.create(linkPayload);
      }

      await Appointments.findByIdAndUpdate(appointmentId, {
        paymentLinkId: link._id.toString(),
        paymentStatus: (appointment.amountPaid || 0) > 0
          ? appointment.paymentStatus
          : 'LINK_SENT',
        amountDue: paymentAmount,
        paymentMethod: 'LINK',
      });

      await logActivity(req, updated ? 'UPDATE' : 'GENERATE', 'PAYMENT_LINK', link._id.toString(), reference);

      return res.status(updated ? 200 : 201).json({
        success: true,
        response: link,
        message: updated ? 'Paystack payment link updated' : 'Paystack payment link generated',
      });
    } catch (error) {
      if (error instanceof PaystackError) {
        return res.status(error.statusCode).json({ success: false, message: error.message });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async verify(req: Request, res: Response) {
    try {
      let reference = String(req.body.reference || req.params.reference || '').trim();

      if (!reference && req.body.appointmentId) {
        const appointment = await Appointments.findById(req.body.appointmentId);
        if (!appointment?.paymentLinkId) {
          return res.status(404).json({ success: false, message: 'No payment link on appointment' });
        }
        const link = await PaymentLink.findById(appointment.paymentLinkId);
        if (!link?.reference) {
          return res.status(404).json({ success: false, message: 'Payment link reference not found' });
        }
        reference = link.reference;
      }

      if (!reference) {
        return res.status(400).json({ success: false, message: 'Reference or appointmentId required' });
      }

      const verification = await verifyTransaction(reference);
      if (verification.status !== 'success') {
        return res.status(200).json({
          success: true,
          paid: false,
          message: `Payment status: ${verification.status}`,
          response: verification,
        });
      }

      const paidAmount = paidAmountFromVerification(verification, 0);
      const result = await settlePaymentByReference(reference, paidAmount);

      if (result.settled) {
        await logActivity(req, 'VERIFY', 'PAYMENT_LINK', reference, `GHS ${paidAmount}`);
      }

      return res.status(200).json({
        success: true,
        paid: result.settled,
        message: result.reason,
        response: {
          verification,
          link: result.link,
          appointment: result.appointment,
        },
      });
    } catch (error) {
      if (error instanceof PaystackError) {
        return res.status(error.statusCode).json({ success: false, message: error.message });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async handleWebhook(req: Request, res: Response) {
    try {
      const rawBody = Buffer.isBuffer(req.body) ? req.body.toString('utf8') : JSON.stringify(req.body);
      const signature = req.headers['x-paystack-signature'] as string | undefined;

      if (!verifyWebhookSignature(rawBody, signature)) {
        return res.status(401).json({ success: false, message: 'Invalid signature' });
      }

      const event = JSON.parse(rawBody);
      const eventType = event?.event;
      const data = event?.data;

      if (eventType === 'charge.success' && data?.reference) {
        const verification = await verifyTransaction(data.reference);
        if (verification.status === 'success') {
          const paidAmount = paidAmountFromVerification(verification, 0);
          await settlePaymentByReference(data.reference, paidAmount);
        }
      }

      if (eventType === 'charge.failed' && data?.reference) {
        await PaymentLink.findOneAndUpdate({ reference: data.reference }, { status: 'FAILED' });
      }

      return res.status(200).json({ success: true });
    } catch {
      return res.status(500).json({ success: false, message: 'Webhook error' });
    }
  }
}
