import { Injectable, Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import {
  NotificationAudience,
  NotificationType,
  NotifyInput,
} from '@edr/types';

import { Contract } from './entities/contract.entity';
import { NotificationsService } from '../notifications/notifications.service';
import { NotificationInboxService } from '../notification-inbox/notification-inbox.service';
import { resolveCompanyNotifyPhone } from '../notifications/resolve-company-phone.util';

/**
 * Customer + staff notifications for the contract lifecycle. Every customer
 * event fans out over three channels: SMS + email (direct, via
 * {@link NotificationsService}) and a persisted in-app notification (via
 * {@link NotificationInboxService}) that deep-links to the contract detail page.
 * Staff events go to the backoffice inbox. All sends are fire-and-forget and
 * never throw — a notification failure must not break a contract transition.
 */
@Injectable()
export class ContractNotifierService {
  private readonly logger = new Logger(ContractNotifierService.name);

  constructor(
    private readonly notifications: NotificationsService,
    private readonly inbox: NotificationInboxService,
    @InjectDataSource()
    private readonly dataSource: DataSource,
  ) {}

  private ref(c: Contract): string {
    return `${c.reference}${c.isGovernment ? ' (gov)' : ''}`;
  }

  /** Send SMS + email to the contract's company contact; log-only on failure. */
  private async notifyContact(
    c: Contract,
    message: string,
    logLabel: string,
  ): Promise<void> {
    this.logger.log(`${logLabel} — ${this.ref(c)}`);
    const phone = c.companyId
      ? await resolveCompanyNotifyPhone(this.dataSource, c.companyId)
      : null;
    const email = c.company?.email ?? c.company?.generalManagerEmail ?? null;

    if (phone) {
      try {
        await this.notifications.directSend('sms', phone, message);
      } catch (err) {
        this.logger.warn(`SMS failed for ${this.ref(c)}: ${(err as Error).message}`);
      }
    }
    if (email) {
      try {
        await this.notifications.directSend('email', email, message);
      } catch (err) {
        this.logger.warn(`Email failed for ${this.ref(c)}: ${(err as Error).message}`);
      }
    }
    if (!phone && !email) {
      this.logger.warn(`No contact on file for ${this.ref(c)} — notification not sent`);
    }
  }

  /** Persist + push an in-app item to all portal users of the contract's company. */
  private inApp(
    c: Contract,
    title: string,
    body: string,
    overrides: Partial<NotifyInput> = {},
  ): void {
    if (!c.companyId) return; // government/unlinked contracts have no portal users
    void this.inbox.notify({
      recipients: { companyId: c.companyId },
      audience: NotificationAudience.PORTAL,
      type: NotificationType.CONTRACT_STATUS,
      title,
      body,
      link: `/contracts/${c.id}`,
      data: { contractId: c.id, reference: c.reference },
      ...overrides,
    });
  }

  /** Persist + push an in-app item to every backoffice staff user. */
  private inAppStaff(
    c: Contract,
    title: string,
    body: string,
    overrides: Partial<NotifyInput> = {},
  ): void {
    void this.inbox.notify({
      recipients: { allBackoffice: true },
      audience: NotificationAudience.BACKOFFICE,
      type: NotificationType.REQUEST_SUBMITTED,
      title,
      body,
      link: `/dashboard/contract-requests/${c.id}`,
      data: { contractId: c.id, reference: c.reference },
      ...overrides,
    });
  }

  // ── Customer-facing lifecycle events ───────────────────────────────────────

  /** Line staff accepted intake → contract is under approval. */
  accepted(c: Contract): void {
    const msg =
      `Your contract ${c.reference} has been accepted and is now under approval. ` +
      `We will notify you once it is approved.`;
    void this.notifyContact(c, msg, 'ACCEPTED');
    this.inApp(c, 'Contract accepted', msg);
  }

  /** All approval steps complete → contract approved. */
  approved(c: Contract): void {
    const msg =
      `Your contract ${c.reference} has been approved. ` +
      `The final document will be prepared for signing.`;
    void this.notifyContact(c, msg, 'APPROVED');
    this.inApp(c, 'Contract approved', msg);
  }

  /** Fully executed (all parties signed) → contract active, customer can book. */
  signedActive(c: Contract): void {
    const msg =
      `Your contract ${c.reference} has been signed and is now active. ` +
      `You can start booking shipments from the portal.`;
    void this.notifyContact(c, msg, 'SIGNED / ACTIVE');
    this.inApp(c, 'Contract active', msg);
  }

  /** Staff rejected the contract. */
  rejected(c: Contract, reason: string): void {
    const msg =
      `Your contract ${c.reference} was rejected. Reason: ${reason}. ` +
      `Please contact us for details.`;
    void this.notifyContact(c, msg, 'REJECTED');
    this.inApp(c, 'Contract rejected', msg);
  }

  /**
   * A later approver sent the contract back to an earlier stage of the chain.
   * Staff-only: the customer is not involved in an internal send-back — their
   * contract simply stays "under approval".
   */
  sentBackToStep(c: Contract, targetRole: string, reason: string): void {
    this.inAppStaff(
      c,
      'Contract returned in approval chain',
      `Contract ${c.reference} was sent back to the ${targetRole} step. Reason: ${reason}`,
    );
  }

  /** Staff requested changes before approval. */
  changesRequested(c: Contract, note: string): void {
    const msg =
      `Changes were requested on your contract ${c.reference}: ${note}. ` +
      `Please update and resubmit from the portal.`;
    void this.notifyContact(c, msg, 'CHANGES REQUESTED');
    this.inApp(c, 'Contract changes requested', msg);
  }

  /** GL rejected a shipment request filed under the contract. */
  shipmentRequestRejected(c: Contract, requestRef: string, note?: string): void {
    const msg =
      `Your shipment request ${requestRef} under contract ${c.reference} was rejected.` +
      (note ? ` Reason: ${note}.` : '') +
      ` Please contact us for details.`;
    void this.notifyContact(c, msg, 'SHIPMENT REQUEST REJECTED');
    this.inApp(c, 'Shipment request rejected', msg, {
      type: NotificationType.BOOKING_STATUS,
      data: { contractId: c.id, reference: requestRef },
    });
  }

  // ── Clearance milestones needing customer action ──────────────────────────

  /** GL advised duty & tax on the contract cycle — customer pays + uploads slip. */
  dutyAdvised(c: Contract, amount: number, currency: string): void {
    const msg =
      `Duty & tax of ${amount} ${currency} has been advised for contract ${c.reference}. ` +
      `Please pay and upload the payment slip from the portal.`;
    void this.notifyContact(c, msg, 'DUTY ADVISED');
    this.inApp(c, 'Duty & tax advised', msg, {
      type: NotificationType.INVOICE_ISSUED,
      link: `/contracts/${c.id}/clearance`,
    });
  }

  /**
   * GL Ethiopia asked Djibouti to name the transit officer. Staff-only, and
   * deep-linked to the Djibouti clearance page where the name is entered — the
   * customs declaration is blocked until they answer.
   */
  transitAssigneeRequested(c: Contract, note: string | null): void {
    const msg =
      `GL Ethiopia needs a transit assignee for contract ${c.reference} before ` +
      `the customs declaration can be filed.${note ? ` Note: "${note}"` : ''}`;
    this.logger.log(`TRANSIT ASSIGNEE REQUESTED — ${c.reference}`);
    this.inAppStaff(c, `Transit assignee needed — ${c.reference}`, msg, {
      type: NotificationType.CLEARANCE_REVIEW,
      link: `/dashboard/gl-djibouti/clearance/${c.id}`,
    });
  }

  /** Djibouti named (or changed) the transit officer — Ethiopia can proceed. */
  transitAssigneeAssigned(
    c: Contract,
    assignee: string,
    previous: string | null,
  ): void {
    const msg = previous
      ? `GL Djibouti changed the transit assignee for contract ${c.reference} from ` +
        `"${previous}" to "${assignee}".`
      : `GL Djibouti assigned ${assignee} to handle contract ${c.reference} in transit. ` +
        `The customs declaration can now be filed.`;
    this.logger.log(`TRANSIT ASSIGNEE ASSIGNED — ${c.reference}`);
    this.inAppStaff(c, `Transit assignee set — ${c.reference}`, msg, {
      type: NotificationType.CLEARANCE_REVIEW,
      link: `/dashboard/contracts/clearance/${c.id}`,
    });
  }

  /**
   * The customer disputed the advised duty & tax. This goes to STAFF, not the
   * customer: GL Ethiopia is the one who has to re-advise, and the clearance
   * page is where they do it.
   */
  dutyDisputed(c: Contract, note: string): void {
    const msg =
      `The customer disputed the duty & tax advised on contract ${c.reference}: ` +
      `"${note}". Review and re-advise the amount on the clearance page.`;
    this.logger.log(`DUTY DISPUTED — ${c.reference}`);
    this.inAppStaff(c, `Duty disputed on ${c.reference}`, msg, {
      type: NotificationType.CLEARANCE_REVIEW,
      link: `/dashboard/contracts/clearance/${c.id}`,
    });
  }

  /** A clearance document was queried — customer must re-upload it. */
  clearanceDocumentQueried(c: Contract, fileKey: string, note: string): void {
    const msg =
      `A clearance document on contract ${c.reference} needs attention: "${fileKey}". ` +
      `${note}. Please re-upload from the portal.`;
    void this.notifyContact(c, msg, 'CLEARANCE DOC QUERIED');
    this.inApp(c, 'Clearance document queried', msg, {
      type: NotificationType.DOCUMENT_ACTION,
      link: `/contracts/${c.id}/clearance`,
    });
  }

  /** Import pre-clearance finalized — the process moves to GL Djibouti collection. */
  preClearanceFinalized(c: Contract): void {
    const msg =
      `Pre-clearance for contract ${c.reference} is complete. ` +
      `Your shipment is proceeding to document collection in Djibouti.`;
    void this.notifyContact(c, msg, 'PRE-CLEARANCE FINALIZED');
    this.inApp(c, 'Pre-clearance complete', msg, {
      type: NotificationType.CLEARANCE_DECISION,
      link: `/contracts/${c.id}/clearance`,
    });
  }

  // ── Staff-facing (backoffice inbox) ────────────────────────────────────────

  /** Customer submitted a contract for review. */
  submittedToStaff(c: Contract): void {
    this.inAppStaff(
      c,
      'New contract submitted',
      `Contract ${this.ref(c)} was submitted and is awaiting intake review.`,
    );
  }

  /** Customer signed the contract — staff counter-sign is next. */
  customerSignedToStaff(c: Contract): void {
    this.inAppStaff(
      c,
      'Customer signed contract',
      `Contract ${this.ref(c)} was signed by the customer and awaits the EDR counter-signature.`,
      { link: `/dashboard/contract-requests/${c.id}/view` },
    );
  }

  /** Customer uploaded clearance documents — GL review is next. */
  clearanceDocsUploadedToStaff(c: Contract): void {
    this.inAppStaff(
      c,
      'Clearance documents uploaded',
      `Customer uploaded clearance documents for contract ${this.ref(c)} — review them in the clearance queue.`,
      {
        type: NotificationType.CLEARANCE_REVIEW,
        link: `/dashboard/contracts/clearance/${c.id}`,
      },
    );
  }

  /** Customer uploaded the duty/tax payment slip — GL verifies it. */
  dutySlipUploadedToStaff(c: Contract): void {
    this.inAppStaff(
      c,
      'Duty slip uploaded',
      `Customer uploaded the duty & tax payment slip for contract ${this.ref(c)}.`,
      {
        type: NotificationType.PAYMENT_RECEIVED,
        link: `/dashboard/contracts/clearance/${c.id}`,
      },
    );
  }

  /** Customer filed a shipment request under a GENERAL customs contract. */
  shipmentRequestedToStaff(c: Contract, requestId: string, requestRef: string): void {
    this.inAppStaff(
      c,
      'New shipment request',
      `Shipment request ${requestRef} was filed under contract ${this.ref(c)} and awaits GL review.`,
      {
        link: `/dashboard/shipment-requests/${requestId}`,
        data: { contractId: c.id, requestId, reference: requestRef },
      },
    );
  }
}
