import { Request, Response } from 'express';
import Customer from '../schema/customer.schema';
import Appointments from '../../appointments/schema/appointments.schema';
import { buildTextSearch, parsePagination, paginationMeta } from '../../../helpers/query.helper';
import { logActivity } from '../../../helpers/activityLog.helper';
import { backfillCustomersFromAppointments, upsertCustomer } from '../../../helpers/customer.helper';

async function attachCustomerStats(customers: unknown[]) {
  const docs = customers.map((customer) => {
    const record = customer as { _id: { toString: () => string }; toObject?: () => Record<string, unknown> };
    return record.toObject ? record.toObject() : (customer as Record<string, unknown>);
  });
  const ids = docs.map((customer) => String(customer._id));
  const stats = await Appointments.aggregate([
    { $match: { customerId: { $in: ids } } },
    {
      $group: {
        _id: '$customerId',
        appointmentCount: { $sum: 1 },
        totalPaid: { $sum: '$amountPaid' },
        totalRefunded: { $sum: '$amountRefunded' },
        totalDue: { $sum: '$amountDue' },
        lastVisit: { $max: '$date' },
        lastService: { $last: '$service' },
      },
    },
  ]);

  const statsMap = new Map(stats.map((row) => [String(row._id), row]));

  return docs.map((plain) => {
    const summary = (statsMap.get(String(plain._id)) || {
      appointmentCount: 0,
      totalPaid: 0,
      totalRefunded: 0,
      totalDue: 0,
      lastVisit: null,
      lastService: null,
    }) as Record<string, unknown>;
    return Object.assign({}, plain, summary);
  });
}

export class CustomersController {
  static async list(req: Request, res: Response) {
    try {
      const { page, limit, skip } = parsePagination(req, 10);
      const filter: Record<string, unknown> = {};
      const search = buildTextSearch(['fullName', 'email', 'phone', 'notes'], String(req.query.q || ''));
      if (search.$or) Object.assign(filter, search);

      let [customers, total] = await Promise.all([
        Customer.find(filter).sort({ updatedAt: -1 }).skip(skip).limit(limit),
        Customer.countDocuments(filter),
      ]);

      if (total === 0 && req.query.backfill !== '0') {
        await backfillCustomersFromAppointments();
        [customers, total] = await Promise.all([
          Customer.find(filter).sort({ updatedAt: -1 }).skip(skip).limit(limit),
          Customer.countDocuments(filter),
        ]);
      }

      const response = await attachCustomerStats(customers);

      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 getOne(req: Request, res: Response) {
    try {
      const customer = await Customer.findById(req.params.id).lean();
      if (!customer) {
        return res.status(404).json({ success: false, message: 'Customer not found' });
      }

      const appointments = await Appointments.find({
        $or: [
          { customerId: String(customer._id) },
          { email: customer.email },
          { phone: customer.phone },
        ],
      })
        .sort({ createdAt: -1 })
        .lean();

      const summary = appointments.reduce(
        (acc, item) => {
          acc.appointmentCount += 1;
          acc.totalPaid += item.amountPaid || 0;
          acc.totalRefunded += item.amountRefunded || 0;
          acc.totalDue += item.amountDue || 0;
          if (!acc.lastVisit || String(item.date) > String(acc.lastVisit)) {
            acc.lastVisit = item.date;
            acc.lastService = item.service;
          }
          return acc;
        },
        {
          appointmentCount: 0,
          totalPaid: 0,
          totalRefunded: 0,
          totalDue: 0,
          lastVisit: null as string | null,
          lastService: null as string | null,
        }
      );

      return res.status(200).json({
        success: true,
        response: {
          customer,
          appointments,
          summary,
        },
      });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async create(req: Request, res: Response) {
    try {
      const { fullName, email, phone, notes, tags } = req.body;
      if (!fullName || !phone) {
        return res.status(400).json({ success: false, message: 'Name and phone are required' });
      }

      const customer = await upsertCustomer({ fullName, email: email || '', phone, notes });
      if (Array.isArray(tags)) customer.tags = tags;
      if (notes) customer.notes = notes;
      await customer.save();

      await logActivity(req, 'CREATE', 'CUSTOMER', customer._id.toString(), customer.fullName);

      return res.status(201).json({ success: true, response: customer });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async update(req: Request, res: Response) {
    try {
      const customer = await Customer.findById(req.params.id);
      if (!customer) {
        return res.status(404).json({ success: false, message: 'Customer not found' });
      }

      const { fullName, email, phone, notes, tags } = req.body;
      if (fullName) customer.fullName = String(fullName).trim();
      if (email) customer.email = String(email).trim().toLowerCase();
      if (phone) customer.phone = String(phone).trim();
      if (notes !== undefined) customer.notes = notes;
      if (Array.isArray(tags)) customer.tags = tags;

      await customer.save();
      await logActivity(req, 'UPDATE', 'CUSTOMER', customer._id.toString(), customer.fullName);

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

  static async backfill(req: Request, res: Response) {
    try {
      const result = await backfillCustomersFromAppointments();
      await logActivity(req, 'UPDATE', 'CUSTOMER', 'backfill', 'Imported customers from appointments');
      return res.status(200).json({ success: true, response: result });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }
}
