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

import { Company, CompanyStatus } from "./entities/company.entity";
import { NotificationsService } from "../notifications/notifications.service";
import { NotificationInboxService } from "../notification-inbox/notification-inbox.service";
import { resolveCompanyNotifyPhone } from "../notifications/resolve-company-phone.util";

/** Account statuses that lock the customer out and therefore must be told to them. */
const PUNITIVE_STATUSES: readonly CompanyStatus[] = [
  CompanyStatus.Suspended,
  CompanyStatus.Blacklisted,
];

/**
 * Customer notifications for company account-status changes. Mirrors
 * {@link ContractNotifierService}: SMS + email direct to the company contact,
 * plus a persisted in-app item. Every send is fire-and-forget and never throws —
 * a notification failure must not roll back the status change itself.
 */
@Injectable()
export class CompanyNotifierService {
  private readonly logger = new Logger(CompanyNotifierService.name);

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

  /** Send SMS + email to the company contact; log-only on failure. */
  private async notifyContact(company: Company, message: string): Promise<void> {
    const phone = await resolveCompanyNotifyPhone(this.dataSource, company.id);
    const email = company.email ?? company.generalManagerEmail ?? null;

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

  /** SMS + email + in-app account-status item to the company contact. */
  private notifyAccount(
    company: Company,
    title: string,
    body: string,
    link = "/settings",
  ): void {
    void this.notifyContact(company, `${title}. ${body}`);
    void this.inbox.notify({
      recipients: { companyId: company.id },
      audience: NotificationAudience.PORTAL,
      type: NotificationType.ACCOUNT_STATUS,
      title,
      body,
      link,
      data: { companyId: company.id, status: company.status },
      priority: NotificationPriority.HIGH,
    });
  }

  /**
   * Tell the customer their account changed status. Fires on the transitions
   * that change what they can do: suspended/blacklisted (locked out) and
   * reactivated (back to Active from a lockout). Silent otherwise.
   */
  statusChanged(company: Company, previous: CompanyStatus): void {
    const status = company.status;
    if (status === previous) return;

    if (status === CompanyStatus.Active && PUNITIVE_STATUSES.includes(previous)) {
      this.logger.log(`ACCOUNT_REACTIVATED — ${company.id}`);
      this.notifyAccount(
        company,
        "Account reactivated",
        "Your company account has been reactivated. " +
          "You can submit new contracts and bookings again.",
      );
      return;
    }

    if (!PUNITIVE_STATUSES.includes(status)) return;

    const label = status === CompanyStatus.Suspended ? "suspended" : "blacklisted";
    this.logger.log(`ACCOUNT_${label.toUpperCase()} — ${company.id}`);
    this.notifyAccount(
      company,
      `Account ${label}`,
      `Your company account has been ${label}. ` +
        `You will not be able to submit new contracts or bookings. ` +
        `Please contact EDR support for assistance.`,
    );
  }

  /**
   * Tell the customer their company account was approved and is now live — the
   * first operational role clearing review promotes a pending company to Active.
   */
  companyApproved(company: Company): void {
    this.logger.log(`ACCOUNT_APPROVED — ${company.id}`);
    this.notifyAccount(
      company,
      "Account approved",
      "Your company account has been approved and is now active. " +
        "You can start submitting bookings and contracts.",
      "/dashboard",
    );
  }

  /**
   * Tell the customer one of their operational roles changed review status —
   * approved, rejected, suspended, or reactivated — quoting the staff message
   * when one was given (rejection/suspension/reactivation require one; approval
   * carries none).
   */
  profileStatusChanged(
    company: Company,
    profileType: string,
    change: "approved" | "rejected" | "suspended" | "reactivated",
    staffMessage: string,
  ): void {
    const title = `${profileType} role ${change}`;
    const consequence: Record<typeof change, string> = {
      approved: "You can now operate under this role.",
      rejected:
        "You will not be able to operate under this role. Amend the required " +
        "documents and resubmit it for approval from your settings page.",
      suspended:
        "You will not be able to operate under this role until it is " +
        "reactivated; your other roles are unaffected.",
      reactivated: "You can operate under this role again.",
    };
    const message = staffMessage.trim();
    const body =
      `Your company's ${profileType} role has been ${change}. ` +
      `${consequence[change]}` +
      (message ? ` Message from EDR staff: ${message}` : "");

    this.logger.log(
      `PROFILE_${change.toUpperCase()} — ${company.id} / ${profileType}`,
    );
    void this.notifyContact(company, `${title}. ${body}`);
    void this.inbox.notify({
      recipients: { companyId: company.id },
      audience: NotificationAudience.PORTAL,
      type: NotificationType.ACCOUNT_STATUS,
      title,
      body,
      link: "/settings",
      data: { companyId: company.id, profileType, change, staffMessage: message },
      priority: NotificationPriority.HIGH,
    });
  }

  // ── Backoffice-facing: work has arrived back in the review queue ────────────

  /**
   * Persist + push an in-app item to every backoffice staff user, deep-linked to
   * the customer's detail page.
   *
   * The recipient resolver has no role/permission targeting (see
   * `notification-recipients.service.ts`) — `allBackoffice` is the narrowest
   * selector available, so marketing is reached by notifying all staff.
   */
  private notifyStaff(
    company: Company,
    title: string,
    body: string,
    data: Record<string, unknown> = {},
  ): void {
    void this.inbox.notify({
      recipients: { allBackoffice: true },
      audience: NotificationAudience.BACKOFFICE,
      type: NotificationType.REQUEST_SUBMITTED,
      title,
      body,
      link: `/dashboard/customers/${company.id}`,
      data: { companyId: company.id, companyName: company.name, ...data },
    });
  }

  /**
   * A customer resubmitted an operational role after it was rejected for
   * adjustment. Without this the role silently flips back to Pending and nobody
   * is told there is anything to look at again.
   */
  roleReapplied(company: Company, profileId: string, profileType: string): void {
    this.logger.log(`ROLE_REAPPLIED — ${company.id} / ${profileId}`);
    this.notifyStaff(
      company,
      "Customer resubmitted a role for approval",
      `${company.name} has adjusted and resubmitted its ${profileType} role. ` +
        `It is back in the pending approval queue for review.`,
      { profileId, profileType },
    );
  }

  /**
   * A customer submitted (or amended and resubmitted) a profile change request.
   * `resubmitted` distinguishes the two so the reviewer knows this is a second
   * look at something they already sent back.
   */
  changeRequestSubmitted(
    company: Company,
    changeRequestId: string,
    resubmitted: boolean,
  ): void {
    this.logger.log(
      `CHANGE_REQUEST_${resubmitted ? "RESUBMITTED" : "SUBMITTED"} — ${company.id}`,
    );
    this.notifyStaff(
      company,
      resubmitted
        ? "Customer resubmitted profile changes"
        : "Customer submitted profile changes",
      resubmitted
        ? `${company.name} has adjusted the changes you sent back and resubmitted ` +
            `them. They are pending your review.`
        : `${company.name} has submitted profile changes that are pending review.`,
      { changeRequestId },
    );
  }

  // ── Customer-facing: a specific document needs correcting ──────────────────

  /**
   * Tell the customer a reviewer wants one specific document corrected. Mirrors
   * the contract `changesRequested` flow: SMS + email out, plus an in-app item
   * deep-linked to the documents tab where they can re-upload.
   */
  documentChangeRequested(
    company: Company,
    documentName: string,
    note: string,
    fileId: string,
  ): void {
    const title = "Document change requested";
    const body =
      `A reviewer has asked you to correct "${documentName}". ` +
      `Reason: ${note} ` +
      `Please upload a corrected version from your settings page.`;

    this.logger.log(`DOCUMENT_CHANGE_REQUESTED — ${company.id} / ${fileId}`);
    void this.notifyContact(company, `${title}. ${body}`);
    void this.inbox.notify({
      recipients: { companyId: company.id },
      audience: NotificationAudience.PORTAL,
      type: NotificationType.DOCUMENT_ACTION,
      title,
      body,
      link: "/settings",
      data: { companyId: company.id, fileId, documentName },
      priority: NotificationPriority.HIGH,
    });
  }
}
