/**
 * Sales Return Application Service
 * Orchestrates return processing workflows
 */

import type {
  SalesReturn,
  SalesReturnItem,
  CreateSalesReturnRecord,
  ApproveSalesReturnRecord,
  ReceiveSalesReturnRecord,
  ProcessSalesReturnRecord,
  RejectSalesReturnRecord,
} from '../domain/sales-return.model';

import {
  calculateSalesReturnTotals,
  validateReturnAgainstInvoice,
  canApproveReturn,
  canReceiveReturn,
  canProcessReturn,
  canRejectReturn,
  canCancelReturn,
  validateReturnRejection,
  validateReturnCancellation,
  getNextStatusAfterApproval,
  getNextStatusAfterReceiving,
  getNextStatusAfterProcessing,
} from '../domain/sales-return.domain';

import { SalesReturnRepository } from './sales-return-repository';
import { SalesReturnAccountingPort } from './sales-return-accounting.port';
import { SalesReturnSideEffectsPort } from './sales-return-side-effects.port';
import { SalesReturnReferenceDataPort } from './sales-return-reference-data.port';
import { SalesReturnIdGeneratorPort } from './sales-return-id-generator.port';

export type CreateSalesReturnInput = CreateSalesReturnRecord & {
  userId: string;
  userName: string;
};

export type ApproveSalesReturnInput = ApproveSalesReturnRecord & {
  userId: string;
  userName: string;
};

export type ReceiveSalesReturnInput = ReceiveSalesReturnRecord & {
  userId: string;
  userName: string;
};

export type ProcessSalesReturnInput = ProcessSalesReturnRecord & {
  userId: string;
  userName: string;
};

export type RejectSalesReturnInput = RejectSalesReturnRecord & {
  userId: string;
  userName: string;
};

export class SalesReturnApplicationService {
  constructor(
    private repository: SalesReturnRepository,
    private accounting: SalesReturnAccountingPort,
    private sideEffects: SalesReturnSideEffectsPort,
    private referenceData: SalesReturnReferenceDataPort,
    private idGenerator: SalesReturnIdGeneratorPort
  ) {}

  /**
   * Static factory for dependency injection
   */
  static createDefault(
    repository: SalesReturnRepository,
    accounting: SalesReturnAccountingPort,
    sideEffects: SalesReturnSideEffectsPort,
    referenceData: SalesReturnReferenceDataPort,
    idGenerator: SalesReturnIdGeneratorPort
  ): SalesReturnApplicationService {
    return new SalesReturnApplicationService(
      repository,
      accounting,
      sideEffects,
      referenceData,
      idGenerator
    );
  }

  /**
   * Create a new sales return
   */
  async create(input: CreateSalesReturnInput): Promise<SalesReturn> {
    // Get original invoice for validation
    const invoice = await this.referenceData.getInvoice(input.invoiceId);
    if (!invoice) {
      throw new Error(`Invoice ${input.invoiceId} not found`);
    }

    // Validate return items against invoice
    const validation = validateReturnAgainstInvoice(invoice as any, input);
    if (!validation.valid) {
      throw new Error(`Validation failed: ${validation.errors.join('; ')}`);
    }

    // Generate return ID and number
    const returnId = await this.idGenerator.generateReturnId();
    const year = new Date(input.date).getFullYear();
    const returnNumber = await this.idGenerator.generateReturnNumber(year);

    // Build return items with calculations
    const items: SalesReturnItem[] = input.items.map((item) => {
      const invoiceLine = (invoice.items || []).find(
        (line) => line.materialId === item.materialId
      );
      return {
        lineId: item.lineId,
        materialId: item.materialId,
        name: invoiceLine?.name || item.materialId,
        returnedQuantity: item.returnedQuantity,
        originalQuantity: invoiceLine?.quantity || 0,
        unitPrice: invoiceLine?.unitPrice || 0,
        returnValue: (item.returnedQuantity * (invoiceLine?.unitPrice || 0)),
        condition: item.condition,
        conditionNotes: item.conditionNotes,
        warehouseId: invoiceLine?.warehouseId,
        shelfId: invoiceLine?.shelfId,
        serialNumber: undefined,
        rfidCode: undefined,
        restockable: item.condition === 'good',
        restockedDate: undefined,
        restockWarehouseId: undefined,
        restockShelfId: undefined,
        inspectionDate: undefined,
        inspectionBy: undefined,
        inspectionNotes: undefined,
      };
    });

    // Calculate totals
    const totals = calculateSalesReturnTotals(items);

    // Create activity log - handle async ID generation outside
    const logId = await this.idGenerator.generateReturnId();
    const activityLog = [
      {
        id: logId,
        timestamp: new Date().toISOString(),
        action: 'created' as const,
        message: `Return created for invoice ${invoice.invoiceNumber}`,
        by: input.userId,
        byName: input.userName,
        details: { reason: input.primaryReason },
      },
    ];

    // Handle document uploads
    const documents = input.documents ? await Promise.all(
      input.documents.map(async (doc) => ({
        id: await this.idGenerator.generateReturnId(),
        ...doc,
        uploadedAt: new Date().toISOString(),
        uploadedBy: input.userId,
      }))
    ) : [];

    // Create the return entity
    const salesReturn: SalesReturn = {
      id: returnId,
      returnNumber,
      date: input.date,
      invoiceId: input.invoiceId,
      invoiceNumber: invoice.invoiceNumber,
      originalInvoiceDate: invoice.date,
      customerId: invoice.customerId,
      customerName: '',
      items,
      subTotal: totals.subTotal,
      taxAmount: totals.taxAmount,
      totalAmount: totals.totalAmount,
      status: 'pending',
      primaryReason: input.primaryReason,
      reasonDescription: input.reasonDescription,
      customerNotes: input.customerNotes,
      internalNotes: input.internalNotes,
      documents,
      activityLog,
      createdBy: input.userId,
      createdAt: new Date().toISOString(),
    };

    // Get customer name
    const customer = await this.referenceData.getCustomer(invoice.customerId);
    if (customer) {
      (salesReturn as any).customerName = customer.name;
    }

    // Persist
    return this.repository.create(salesReturn as any);
  }

  /**
   * Approve a return
   */
  async approve(input: ApproveSalesReturnInput): Promise<SalesReturn> {
    const salesReturn = await this.repository.getById(input.returnId);
    if (!salesReturn) {
      throw new Error(`Return ${input.returnId} not found`);
    }

    if (!canApproveReturn(salesReturn.status)) {
      throw new Error(
        `Cannot approve return with status: ${salesReturn.status}`
      );
    }

    const nextStatus = getNextStatusAfterApproval(salesReturn.status);
    const updatedReturn = await this.repository.update(input.returnId, {
      status: nextStatus,
      approvalDate: input.approvalDate,
      approvalBy: input.userId,
      approvalByName: input.userName,
    });

    if (!updatedReturn) {
      throw new Error(`Failed to update return ${input.returnId}`);
    }

    // Send notification
    await this.sideEffects.notifyReturnApproved(updatedReturn);

    // Log activity
    await this.sideEffects.logActivity({
      returnId: input.returnId,
      action: 'approved',
      message: 'Return approved',
      by: input.userId,
      byName: input.userName,
    });

    return updatedReturn;
  }

  /**
   * Receive a returned shipment
   */
  async receive(input: ReceiveSalesReturnInput): Promise<SalesReturn> {
    const salesReturn = await this.repository.getById(input.returnId);
    if (!salesReturn) {
      throw new Error(`Return ${input.returnId} not found`);
    }

    if (!canReceiveReturn(salesReturn.status)) {
      throw new Error(
        `Cannot receive return with status: ${salesReturn.status}`
      );
    }

    const nextStatus = getNextStatusAfterReceiving(salesReturn.status);
    const updatedReturn = await this.repository.update(input.returnId, {
      status: nextStatus,
      receivedDate: input.receivedDate,
      receivedBy: input.userId,
      receivedByName: input.userName,
      receivedQuantity: input.receivedQuantity,
      receivedNotes: input.receivedNotes,
    });

    if (!updatedReturn) {
      throw new Error(`Failed to update return ${input.returnId}`);
    }

    // Restock inventory for received, restockable items.
    await this.sideEffects.recordInventoryReceipt(updatedReturn, input.userId);

    // Send notification
    await this.sideEffects.notifyReturnReceived(updatedReturn);

    // Log activity
    await this.sideEffects.logActivity({
      returnId: input.returnId,
      action: 'received',
      message: `Received ${input.receivedQuantity} units`,
      by: input.userId,
      byName: input.userName,
    });

    return updatedReturn;
  }

  /**
   * Process a return (issue credit note/refund)
   */
  async process(input: ProcessSalesReturnInput): Promise<SalesReturn> {
    const salesReturn = await this.repository.getById(input.returnId);
    if (!salesReturn) {
      throw new Error(`Return ${input.returnId} not found`);
    }

    if (!canProcessReturn(salesReturn.status)) {
      throw new Error(
        `Cannot process return with status: ${salesReturn.status}`
      );
    }

    // Record in accounting
    const accountingResult = await this.accounting.recordReturnCreditNote({
      returnId: input.returnId,
      invoiceId: salesReturn.invoiceId,
      customerId: salesReturn.customerId,
      amount: input.refundAmount,
      date: input.processedDate,
      description: `Credit note for return ${salesReturn.returnNumber}`,
    });

    // Record refund
    await this.accounting.recordRefund({
      returnId: input.returnId,
      amount: input.refundAmount,
      date: input.processedDate,
      refundMethod: input.refundMethod,
      description: `Refund for return ${salesReturn.returnNumber}`,
    });

    const nextStatus = getNextStatusAfterProcessing(salesReturn.status);
    const updatedReturn = await this.repository.update(input.returnId, {
      status: nextStatus,
      processedDate: input.processedDate,
      processedBy: input.userId,
      processedByName: input.userName,
      creditNoteId: accountingResult.creditNoteId,
      creditNoteNumber: accountingResult.creditNoteId,
      refundAmount: input.refundAmount,
      refundMethod: input.refundMethod,
      refundReference: input.refundReference,
      refundIssuedDate: input.processedDate,
      processingNotes: input.processingNotes,
    });

    if (!updatedReturn) {
      throw new Error(`Failed to update return ${input.returnId}`);
    }

    // Send notification
    await this.sideEffects.notifyRefundIssued(
      updatedReturn,
      input.refundAmount,
      input.refundMethod
    );

    // Log activity
    await this.sideEffects.logActivity({
      returnId: input.returnId,
      action: 'processed',
      message: `Refund of ${input.refundAmount} issued via ${input.refundMethod}`,
      by: input.userId,
      byName: input.userName,
    });

    return updatedReturn;
  }

  /**
   * Reject a return
   */
  async reject(input: RejectSalesReturnInput): Promise<SalesReturn> {
    const salesReturn = await this.repository.getById(input.returnId);
    if (!salesReturn) {
      throw new Error(`Return ${input.returnId} not found`);
    }

    const validation = validateReturnRejection(
      salesReturn.status,
      input.rejectionReason
    );
    if (!validation.valid) {
      throw new Error(`Validation failed: ${validation.errors.join('; ')}`);
    }

    const updatedReturn = await this.repository.update(input.returnId, {
      status: 'rejected',
      rejectionReason: input.rejectionReason,
    });

    if (!updatedReturn) {
      throw new Error(`Failed to update return ${input.returnId}`);
    }

    // Send notification
    await this.sideEffects.notifyReturnRejected(
      updatedReturn,
      input.rejectionReason
    );

    // Log activity
    await this.sideEffects.logActivity({
      returnId: input.returnId,
      action: 'rejected',
      message: `Return rejected: ${input.rejectionReason}`,
      by: input.userId,
      byName: input.userName,
    });

    return updatedReturn;
  }

  /**
   * Get all returns
   */
  async list(): Promise<SalesReturn[]> {
    return this.repository.list();
  }

  /**
   * Get return by ID
   */
  async getById(id: string): Promise<SalesReturn | null> {
    return this.repository.getById(id);
  }

  /**
   * Get returns for an invoice
   */
  async getByInvoiceId(invoiceId: string): Promise<SalesReturn[]> {
    return this.repository.getByInvoiceId(invoiceId);
  }

  /**
   * Get returns for a customer
   */
  async getByCustomerId(customerId: string): Promise<SalesReturn[]> {
    return this.repository.getByCustomerId(customerId);
  }

  /**
   * Get returns with specific status
   */
  async getByStatus(status: string): Promise<SalesReturn[]> {
    return this.repository.getByStatus(status);
  }
}
