/**
 * Sales Invoice PostgreSQL Repository Adapter
 * Implements SalesInvoiceRepository using PostgreSQL storage
 */

import { pgGetInvoices, pgSaveInvoices } from '@/lib/postgres/data-access';
import {
  SalesInvoice,
  SalesInvoicePaymentMethod,
} from '../domain/sales-invoice.model';
import {
  SalesInvoiceRepository,
  CreateSalesInvoiceDto,
} from '../application/sales-invoice-repository';

type StoredInvoice = SalesInvoice & {
  _type: 'sales-invoice';
};

export class SalesInvoicePostgresAdapter implements SalesInvoiceRepository {
  private async loadInvoices(): Promise<StoredInvoice[]> {
    const result = await pgGetInvoices({ page: 1, pageSize: 20000 });
    const rows = (result.items || []) as Record<string, unknown>[];
    return rows
      .filter((inv) => {
        const type = String((inv as { _type?: string })._type || '');
        if (type === 'sales-invoice') return true;
        // Also include legacy POS invoices by invoice number prefix, even when _type differs.
        const invNum = String((inv as { invoiceNumber?: string }).invoiceNumber || '');
        return invNum.startsWith('S/');
      })
      .map((inv) => ({ ...inv, _type: 'sales-invoice' } as StoredInvoice));
  }

  private async saveInvoices(invoices: StoredInvoice[]): Promise<void> {
    await pgSaveInvoices(invoices as unknown as Array<Record<string, unknown>>);
  }

  async create(dto: CreateSalesInvoiceDto): Promise<SalesInvoice> {
    const invoices = await this.loadInvoices();

    const now = new Date().toISOString();
    const invoice: StoredInvoice = {
      _type: 'sales-invoice',
      ...dto,
      id: dto.id || `sales-${Date.now()}-${Math.random().toString(36).slice(2)}`,
      createdAt: dto.createdAt || now,
      activityLog: [
        {
          id: `log-${Date.now()}`,
          timestamp: now,
          action: 'created',
          message: 'Invoice created',
          by: 'system',
          byName: 'System',
        },
      ],
    } as StoredInvoice;

    invoices.push(invoice);
    await this.saveInvoices(invoices);

    const { _type, ...rest } = invoice;
    return rest;
  }

  async getById(id: string): Promise<SalesInvoice | null> {
    const invoices = await this.loadInvoices();
    const invoice = invoices.find((inv) => inv.id === id);
    if (!invoice) return null;

    const { _type, ...rest } = invoice;
    return rest;
  }

  async getByInvoiceNumber(invoiceNumber: string): Promise<SalesInvoice | null> {
    const invoices = await this.loadInvoices();
    const invoice = invoices.find((inv) => inv.invoiceNumber === invoiceNumber);
    if (!invoice) return null;

    const { _type, ...rest } = invoice;
    return rest;
  }

  async getByCustomerId(
    customerId: string,
    options?: { skip?: number; take?: number }
  ): Promise<SalesInvoice[]> {
    const invoices = await this.loadInvoices();
    let filtered = invoices.filter((inv) => inv.customerId === customerId);

    if (options?.skip) {
      filtered = filtered.slice(options.skip);
    }
    if (options?.take) {
      filtered = filtered.slice(0, options.take);
    }

    return filtered.map(({ _type, ...rest }) => rest);
  }

  async list(options?: {
    skip?: number;
    take?: number;
    status?: string;
    documentType?: 'shipment' | 'tax_invoice';
    customerId?: string;
    search?: string;
    excludeShipments?: boolean;
  }): Promise<{ items: SalesInvoice[]; total: number }> {
    let invoices = await this.loadInvoices();

    if (options?.status) {
      invoices = invoices.filter((inv) => inv.status === options.status);
    }
    if (options?.documentType) {
      invoices = invoices.filter((inv) => (inv.documentType || 'tax_invoice') === options.documentType);
    } else if (options?.excludeShipments) {
      // Exclude pure shipment documents from the default list view
      invoices = invoices.filter((inv) => (inv.documentType || 'tax_invoice') !== 'shipment');
    }
    if (options?.customerId) {
      invoices = invoices.filter((inv) => inv.customerId === options.customerId);
    }
    if (options?.search) {
      const search = options.search.toLowerCase();
      invoices = invoices.filter(
        (inv) =>
          inv.invoiceNumber.toLowerCase().includes(search) ||
          inv.customerName.toLowerCase().includes(search)
      );
    }

    const total = invoices.length;

    if (options?.skip) {
      invoices = invoices.slice(options.skip);
    }
    if (options?.take) {
      invoices = invoices.slice(0, options.take);
    }

    const items = invoices.map(({ _type, ...rest }) => rest);
    return { items, total };
  }

  async update(id: string, updates: Partial<SalesInvoice>): Promise<SalesInvoice> {
    const invoices = await this.loadInvoices();
    const index = invoices.findIndex((inv) => inv.id === id);

    if (index === -1) {
      throw new Error(`Invoice not found: ${id}`);
    }

    invoices[index] = { ...invoices[index], ...updates };
    await this.saveInvoices(invoices);

    const { _type, ...rest } = invoices[index];
    return rest;
  }

  async lock(id: string, userId: string, userName: string): Promise<SalesInvoice> {
    return this.update(id, {
      lockedById: userId,
      lockedByName: userName,
      lockedAt: new Date().toISOString(),
    });
  }

  async unlock(id: string): Promise<SalesInvoice> {
    return this.update(id, {
      lockedById: undefined,
      lockedByName: undefined,
      lockedAt: undefined,
    });
  }

  async cancel(
    id: string,
    reason: string,
    cancelledBy: string,
    cancelledByName: string
  ): Promise<SalesInvoice> {
    return this.update(id, {
      status: 'cancelled',
      cancelledReason: reason,
      cancelledBy,
      cancelledAt: new Date().toISOString(),
    });
  }

  async markAsShipped(
    id: string,
    shipmentId: string,
    shipmentDate: string,
    shippedBy: string,
    shippedByName: string
  ): Promise<SalesInvoice> {
    return this.update(id, {
      shipmentId,
      shipmentDate,
      shippingStatus: 'shipped',
      shippedBy,
      shippedAt: new Date().toISOString(),
    });
  }

  async markAsDelivered(
    id: string,
    deliveredAt: string,
    deliveredBy: string
  ): Promise<SalesInvoice> {
    return this.update(id, {
      actualDeliveryDate: deliveredAt,
      shippingStatus: 'delivered',
      shippedBy: deliveredBy,
      shippedAt: deliveredAt,
    });
  }

  async recordPayment(
    id: string,
    amount: number,
    method: SalesInvoicePaymentMethod,
    recordedBy: string
  ): Promise<SalesInvoice> {
    const invoice = await this.getById(id);
    if (!invoice) {
      throw new Error(`Invoice not found: ${id}`);
    }

    const newAmountReceived = (invoice.amountReceived || 0) + amount;
    const newAmountDue = Math.max(0, invoice.grandTotal - newAmountReceived);

    let paymentStatus: string;
    if (newAmountReceived >= invoice.grandTotal) {
      paymentStatus = 'paid';
    } else if (newAmountReceived > 0) {
      paymentStatus = 'partial';
    } else {
      paymentStatus = 'due';
    }

    const updatedMethods = [
      ...(invoice.paymentMethods || []),
      method,
    ];

    return this.update(id, {
      amountReceived: newAmountReceived,
      amountDue: newAmountDue,
      paymentStatus: paymentStatus as 'due' | 'partial' | 'paid' | 'overdue',
      paymentMethods: updatedMethods,
    });
  }

  async addActivityLog(
    id: string,
    action: string,
    message: string,
    by: string,
    byName?: string,
    details?: Record<string, unknown>
  ): Promise<void> {
    const invoice = await this.getById(id);
    if (!invoice) {
      throw new Error(`Invoice not found: ${id}`);
    }

    const newEntry = {
      id: `log-${Date.now()}`,
      timestamp: new Date().toISOString(),
      action: action as 'created' | 'updated' | 'posted' | 'shipped' | 'received' | 'cancelled' | 'notes',
      message,
      by,
      byName,
      details,
    };

    const log = [...(invoice.activityLog || []), newEntry];
    await this.update(id, { activityLog: log });
  }
}
