import { PortCode } from '../value-objects/port-code.vo';

/**
 * CSV Booking Status Enum
 *
 * Represents the lifecycle of a CSV-based booking request
 */
export enum CsvBookingStatus {
  PENDING_PAYMENT = 'PENDING_PAYMENT', // Awaiting commission payment
  PENDING_BANK_TRANSFER = 'PENDING_BANK_TRANSFER', // Bank transfer declared, awaiting admin validation
  PENDING = 'PENDING', // Awaiting carrier response
  ACCEPTED = 'ACCEPTED', // Carrier accepted the booking
  REJECTED = 'REJECTED', // Carrier rejected the booking
  CANCELLED = 'CANCELLED', // User cancelled the booking
}

/**
 * Document Interface
 *
 * Represents a document attached to a booking
 */
export interface CsvBookingDocument {
  id: string;
  type: DocumentType;
  fileName: string;
  filePath: string;
  mimeType: string;
  size: number;
  uploadedAt: Date;
}

/**
 * Document Type Enum
 *
 * Types of documents that can be attached to a booking
 */
export enum DocumentType {
  BILL_OF_LADING = 'BILL_OF_LADING',
  PACKING_LIST = 'PACKING_LIST',
  COMMERCIAL_INVOICE = 'COMMERCIAL_INVOICE',
  CERTIFICATE_OF_ORIGIN = 'CERTIFICATE_OF_ORIGIN',
  OTHER = 'OTHER',
}

/**
 * CSV Booking Entity
 *
 * Domain entity representing a shipping booking request from CSV rate search.
 * This is a simplified booking workflow for CSV-based rates where the user
 * selects a rate and sends a booking request to the carrier with documents.
 *
 * Business Rules:
 * - Booking can only be accepted/rejected when status is PENDING
 * - Once accepted/rejected, status cannot be changed
 * - Booking expires after 7 days if not responded to
 * - At least one document is required for booking creation
 * - Confirmation token is used for email accept/reject links
 * - Only carrier can accept/reject via email link
 * - User can cancel pending bookings
 */
export class CsvBooking {
  constructor(
    public readonly id: string,
    public readonly userId: string,
    public readonly organizationId: string,
    public readonly carrierName: string,
    public readonly carrierEmail: string,
    public readonly origin: PortCode,
    public readonly destination: PortCode,
    public readonly volumeCBM: number,
    public readonly weightKG: number,
    public readonly palletCount: number,
    public readonly priceUSD: number,
    public readonly priceEUR: number,
    public readonly primaryCurrency: string,
    public readonly transitDays: number,
    public readonly containerType: string,
    public status: CsvBookingStatus,
    public readonly documents: CsvBookingDocument[],
    public readonly confirmationToken: string,
    public readonly requestedAt: Date,
    public respondedAt?: Date,
    public notes?: string,
    public rejectionReason?: string,
    public readonly bookingNumber?: string,
    public commissionRate?: number,
    public commissionAmountEur?: number,
    public stripePaymentIntentId?: string
  ) {
    this.validate();
  }

  /**
   * Validate booking data
   */
  private validate(): void {
    if (!this.id || this.id.trim().length === 0) {
      throw new Error('Booking ID is required');
    }

    if (!this.userId || this.userId.trim().length === 0) {
      throw new Error('User ID is required');
    }

    if (!this.organizationId || this.organizationId.trim().length === 0) {
      throw new Error('Organization ID is required');
    }

    if (!this.carrierName || this.carrierName.trim().length === 0) {
      throw new Error('Carrier name is required');
    }

    if (!this.carrierEmail || this.carrierEmail.trim().length === 0) {
      throw new Error('Carrier email is required');
    }

    // Validate email format
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(this.carrierEmail)) {
      throw new Error('Invalid carrier email format');
    }

    if (this.volumeCBM <= 0) {
      throw new Error('Volume must be positive');
    }

    if (this.weightKG <= 0) {
      throw new Error('Weight must be positive');
    }

    if (this.palletCount < 0) {
      throw new Error('Pallet count cannot be negative');
    }

    if (this.priceUSD < 0 || this.priceEUR < 0) {
      throw new Error('Price cannot be negative');
    }

    if (this.transitDays <= 0) {
      throw new Error('Transit days must be positive');
    }

    if (!this.confirmationToken || this.confirmationToken.trim().length === 0) {
      throw new Error('Confirmation token is required');
    }

    if (!this.documents || this.documents.length === 0) {
      throw new Error('At least one document is required for booking');
    }
  }

  /**
   * Apply commission to the booking
   */
  applyCommission(ratePercent: number, baseAmountEur: number): void {
    this.commissionRate = ratePercent;
    this.commissionAmountEur = Math.round(baseAmountEur * ratePercent) / 100;
  }

  /**
   * Mark commission payment as completed → transition to PENDING
   *
   * @throws Error if booking is not in PENDING_PAYMENT status
   */
  markPaymentCompleted(): void {
    if (this.status !== CsvBookingStatus.PENDING_PAYMENT) {
      throw new Error(
        `Cannot mark payment completed for booking with status ${this.status}. Only PENDING_PAYMENT bookings can transition.`
      );
    }

    this.status = CsvBookingStatus.PENDING;
  }

  /**
   * Declare bank transfer → transition to PENDING_BANK_TRANSFER
   * Called when user confirms they have sent the bank transfer
   *
   * @throws Error if booking is not in PENDING_PAYMENT status
   */
  markBankTransferDeclared(): void {
    if (this.status !== CsvBookingStatus.PENDING_PAYMENT) {
      throw new Error(
        `Cannot declare bank transfer for booking with status ${this.status}. Only PENDING_PAYMENT bookings can transition.`
      );
    }

    this.status = CsvBookingStatus.PENDING_BANK_TRANSFER;
  }

  /**
   * Admin validates bank transfer → transition to PENDING
   * Called by admin once bank transfer has been received and verified
   *
   * @throws Error if booking is not in PENDING_BANK_TRANSFER status
   */
  markBankTransferValidated(): void {
    if (this.status !== CsvBookingStatus.PENDING_BANK_TRANSFER) {
      throw new Error(
        `Cannot validate bank transfer for booking with status ${this.status}. Only PENDING_BANK_TRANSFER bookings can transition.`
      );
    }

    this.status = CsvBookingStatus.PENDING;
  }

  /**
   * Accept the booking
   *
   * @throws Error if booking is not in PENDING status
   */
  accept(): void {
    if (this.status !== CsvBookingStatus.PENDING) {
      throw new Error(
        `Cannot accept booking with status ${this.status}. Only PENDING bookings can be accepted.`
      );
    }

    if (this.isExpired()) {
      throw new Error('Cannot accept expired booking');
    }

    this.status = CsvBookingStatus.ACCEPTED;
    this.respondedAt = new Date();
  }

  /**
   * Reject the booking
   *
   * @param reason Optional reason for rejection
   * @throws Error if booking is not in PENDING status
   */
  reject(reason?: string): void {
    if (this.status !== CsvBookingStatus.PENDING) {
      throw new Error(
        `Cannot reject booking with status ${this.status}. Only PENDING bookings can be rejected.`
      );
    }

    if (this.isExpired()) {
      throw new Error('Cannot reject expired booking (already expired)');
    }

    this.status = CsvBookingStatus.REJECTED;
    this.respondedAt = new Date();
    if (reason) {
      this.rejectionReason = reason;
    }
  }

  /**
   * Cancel the booking (by user)
   *
   * @throws Error if booking is already accepted/rejected
   */
  cancel(): void {
    if (this.status === CsvBookingStatus.ACCEPTED) {
      throw new Error('Cannot cancel accepted booking. Contact carrier to cancel.');
    }

    if (this.status === CsvBookingStatus.REJECTED) {
      throw new Error('Cannot cancel rejected booking');
    }

    if (this.status === CsvBookingStatus.CANCELLED) {
      throw new Error('Booking is already cancelled');
    }

    this.status = CsvBookingStatus.CANCELLED;
    this.respondedAt = new Date();
  }

  /**
   * Check if booking has expired (7 days without response)
   *
   * @returns true if booking is older than 7 days and still pending
   */
  isPendingPayment(): boolean {
    return this.status === CsvBookingStatus.PENDING_PAYMENT;
  }

  isExpired(): boolean {
    if (this.status !== CsvBookingStatus.PENDING) {
      return false;
    }

    const expirationDate = new Date(this.requestedAt);
    expirationDate.setDate(expirationDate.getDate() + 7);

    return new Date() > expirationDate;
  }

  /**
   * Check if booking is still pending (awaiting response)
   */
  isPending(): boolean {
    return this.status === CsvBookingStatus.PENDING && !this.isExpired();
  }

  /**
   * Check if booking was accepted
   */
  isAccepted(): boolean {
    return this.status === CsvBookingStatus.ACCEPTED;
  }

  /**
   * Check if booking was rejected
   */
  isRejected(): boolean {
    return this.status === CsvBookingStatus.REJECTED;
  }

  /**
   * Check if booking was cancelled
   */
  isCancelled(): boolean {
    return this.status === CsvBookingStatus.CANCELLED;
  }

  /**
   * Get route description (origin → destination)
   */
  getRouteDescription(): string {
    return `${this.origin.getValue()} → ${this.destination.getValue()}`;
  }

  /**
   * Get booking summary
   */
  getSummary(): string {
    return `CSV Booking ${this.id}: ${this.carrierName} - ${this.getRouteDescription()} (${this.status})`;
  }

  /**
   * Get price in specified currency
   */
  getPriceInCurrency(currency: 'USD' | 'EUR'): number {
    return currency === 'USD' ? this.priceUSD : this.priceEUR;
  }

  /**
   * Get days until expiration (negative if expired)
   */
  getDaysUntilExpiration(): number {
    if (this.status !== CsvBookingStatus.PENDING) {
      return 0;
    }

    const expirationDate = new Date(this.requestedAt);
    expirationDate.setDate(expirationDate.getDate() + 7);

    const now = new Date();
    const diffTime = expirationDate.getTime() - now.getTime();
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));

    return diffDays;
  }

  /**
   * Check if booking has a specific document type
   */
  hasDocumentType(type: DocumentType): boolean {
    return this.documents.some(doc => doc.type === type);
  }

  /**
   * Get documents by type
   */
  getDocumentsByType(type: DocumentType): CsvBookingDocument[] {
    return this.documents.filter(doc => doc.type === type);
  }

  /**
   * Check if all required documents are present
   */
  hasAllRequiredDocuments(): boolean {
    const requiredTypes = [
      DocumentType.BILL_OF_LADING,
      DocumentType.PACKING_LIST,
      DocumentType.COMMERCIAL_INVOICE,
    ];

    return requiredTypes.every(type => this.hasDocumentType(type));
  }

  /**
   * Get response time in hours (if responded)
   */
  getResponseTimeHours(): number | null {
    if (!this.respondedAt) {
      return null;
    }

    const diffTime = this.respondedAt.getTime() - this.requestedAt.getTime();
    const diffHours = diffTime / (1000 * 60 * 60);

    return Math.round(diffHours * 100) / 100; // Round to 2 decimals
  }

  toString(): string {
    return this.getSummary();
  }

  /**
   * Create a CsvBooking from persisted data (skips document validation)
   *
   * Use this when loading from database where bookings might have been created
   * before document requirement was enforced, or documents were lost.
   */
  static fromPersistence(
    id: string,
    userId: string,
    organizationId: string,
    carrierName: string,
    carrierEmail: string,
    origin: PortCode,
    destination: PortCode,
    volumeCBM: number,
    weightKG: number,
    palletCount: number,
    priceUSD: number,
    priceEUR: number,
    primaryCurrency: string,
    transitDays: number,
    containerType: string,
    status: CsvBookingStatus,
    documents: CsvBookingDocument[],
    confirmationToken: string,
    requestedAt: Date,
    respondedAt?: Date,
    notes?: string,
    rejectionReason?: string,
    bookingNumber?: string,
    commissionRate?: number,
    commissionAmountEur?: number,
    stripePaymentIntentId?: string
  ): CsvBooking {
    // Create instance without calling constructor validation
    const booking = Object.create(CsvBooking.prototype);

    // Assign all properties directly
    booking.id = id;
    booking.userId = userId;
    booking.organizationId = organizationId;
    booking.carrierName = carrierName;
    booking.carrierEmail = carrierEmail;
    booking.origin = origin;
    booking.destination = destination;
    booking.volumeCBM = volumeCBM;
    booking.weightKG = weightKG;
    booking.palletCount = palletCount;
    booking.priceUSD = priceUSD;
    booking.priceEUR = priceEUR;
    booking.primaryCurrency = primaryCurrency;
    booking.transitDays = transitDays;
    booking.containerType = containerType;
    booking.status = status;
    booking.documents = documents || [];
    booking.confirmationToken = confirmationToken;
    booking.requestedAt = requestedAt;
    booking.respondedAt = respondedAt;
    booking.notes = notes;
    booking.rejectionReason = rejectionReason;
    booking.bookingNumber = bookingNumber;
    booking.commissionRate = commissionRate;
    booking.commissionAmountEur = commissionAmountEur;
    booking.stripePaymentIntentId = stripePaymentIntentId;

    return booking;
  }
}
