/**
 * Sales Invoice JSON Repository Adapter
 * Implements SalesInvoiceRepository using JSON file storage
 */

import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
import {
  SalesInvoice,
  SalesInvoicePaymentMethod,
} from '../domain/sales-invoice.model';
import {
  SalesInvoiceRepository,
  CreateSalesInvoiceDto,
} from '../application/sales-invoice-repository';

// Data directory
const DATA_DIR = join(process.cwd(), 'db');
const FILE_PATH = join(DATA_DIR, 'invoices.json');

type StoredInvoice = SalesInvoice & {
  _type: 'sales-invoice'; // Discriminator for mixed invoice files
};

export class SalesInvoiceJsonAdapter implements SalesInvoiceRepository {
  /**
   * Load invoices from JSON file
   */
  private async loadInvoices(): Promise<StoredInvoice[]> {
    try {
      const data = await readFile(FILE_PATH, 'utf-8');
      const all = JSON.parse(data) || [];
      // Filter only sales invoices (those with _type: 'sales-invoice')
      return all.filter((inv: any) => inv._type === 'sales-invoice');
    } catch {
      return [];
    }
  }

  /**
   * Save invoices to JSON file
   */
  private async saveInvoices(invoices: StoredInvoice[]): Promise<void> {
    try {
      // Read all invoices first (to preserve non-sales types if mixed)
      let allInvoices: any[] = [];
      try {
        const data = await readFile(FILE_PATH, 'utf-8');
        allInvoices = JSON.parse(data) || [];
      } catch {
        allInvoices = [];
      }

      // Remove old sales invoices and add new ones
      const nonSalesInvoices = allInvoices.filter(
        (inv: any) => inv._type !== 'sales-invoice'
      );
      const merged = [...nonSalesInvoices, ...invoices];

      await writeFile(FILE_PATH, JSON.stringify(merged, null, 2), 'utf-8');
    } catch (error) {
      console.error('Failed to save invoices:', error);
      throw error;
    }
  }

  /**
   * Create a new sales invoice
   */
  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: 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;
  }

  /**
   * Get invoice by ID
   */
  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;
  }

  /**
   * Get invoice by invoice number
   */
  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;
  }

  /**
   * Get invoices by customer ID
   */
  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);
  }

  /**
   * List all invoices with optional filtering
   */
  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();

    // Apply filters
    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) {
      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;

    // Apply pagination
    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 };
  }

  /**
   * Update invoice
   */
  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;
  }

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

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

  /**
   * Cancel invoice
   */
  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(),
    });
  }

  /**
   * Mark invoice as shipped
   */
  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(),
    });
  }

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

  /**
   * Record payment
   */
  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);

    // Determine payment status
    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 any,
      paymentMethods: updatedMethods,
    });
  }

  /**
   * Add activity log entry
   */
  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 any,
      message,
      by,
      byName,
      details,
    };

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