import { type EmptyFields } from "@tailor-platform/erp-kit/core";
import type { TailorAnyDBField } from "@tailor-platform/sdk";
import type { defineModule as defineUserManagementModule } from "../user-management";
import { activateNotificationChannel } from "./command/activateNotificationChannel.generated";
import { anonymizeNotificationsForUser } from "./command/anonymizeNotificationsForUser.generated";
import { archiveNotification } from "./command/archiveNotification.generated";
// Slack destination (optional; gated by the `slack` defineModule option)
import { beginSlackWorkspaceInstall } from "./command/beginSlackWorkspaceInstall.generated";
import { bulkUpdateNotificationPreferences } from "./command/bulkUpdateNotificationPreferences.generated";
import { completeSlackWorkspaceInstall } from "./command/completeSlackWorkspaceInstall.generated";
import { createNotificationChannel } from "./command/createNotificationChannel.generated";
import { createNotificationSubscription } from "./command/createNotificationSubscription.generated";
import { createNotificationTemplate } from "./command/createNotificationTemplate.generated";
import { deactivateNotificationChannel } from "./command/deactivateNotificationChannel.generated";
import { deleteNotificationSubscription } from "./command/deleteNotificationSubscription.generated";
import { deleteNotificationTemplate } from "./command/deleteNotificationTemplate.generated";
import { handleSlackAppUninstalled } from "./command/handleSlackAppUninstalled.generated";
import { logNotificationEvent } from "./command/logNotificationEvent.generated";
import { markAllNotificationsAsRead } from "./command/markAllNotificationsAsRead.generated";
import { markNotificationAsRead } from "./command/markNotificationAsRead.generated";
import { markNotificationsAsSeen } from "./command/markNotificationsAsSeen.generated";
import { recordDeliveryEvent } from "./command/recordDeliveryEvent.generated";
import { resetNotificationPreferences } from "./command/resetNotificationPreferences.generated";
import { runNotificationAuditRetentionSweep } from "./command/runNotificationAuditRetentionSweep.generated";
import { updateNotificationChannel } from "./command/updateNotificationChannel.generated";
import { updateNotificationPreference } from "./command/updateNotificationPreference.generated";
import { updateNotificationTemplate } from "./command/updateNotificationTemplate.generated";
import {
  createChannelRoutingBindingType,
  type CreateChannelRoutingBindingTypeParams,
} from "./db/channelRoutingBinding";
import { createDestinationDeliveryLogType } from "./db/destinationDeliveryLog";
import { eventCategoryBinding } from "./db/eventCategoryBinding";
import { createNotificationType, type CreateNotificationTypeParams } from "./db/notification";
import { notificationCategory } from "./db/notificationCategory";
import { notificationChannel } from "./db/notificationChannel";
import {
  createNotificationDeliveryAuditType,
  type CreateNotificationDeliveryAuditTypeParams,
} from "./db/notificationDeliveryAudit";
import {
  createNotificationEventType,
  type CreateNotificationEventTypeParams,
} from "./db/notificationEvent";
import {
  createNotificationPreferenceType,
  type CreateNotificationPreferenceTypeParams,
} from "./db/notificationPreference";
import {
  createNotificationSubscriptionType,
  type CreateNotificationSubscriptionTypeParams,
} from "./db/notificationSubscription";
import { notificationTemplate } from "./db/notificationTemplate";
import {
  createSlackWorkspaceIntegrationType,
  type CreateSlackWorkspaceIntegrationTypeParams,
} from "./db/slackWorkspaceIntegration";
import {
  dispatchNotificationEvents,
  type DispatchNotificationEventsParams,
} from "./executor/dispatchNotificationEvents";
import {
  redrainNotificationEvents,
  type RedrainNotificationEventsParams,
} from "./executor/redrainNotificationEvents";
import type { DB } from "./generated/kysely-tailordb";
import type { DispatchAdapters } from "./lib/dispatchAdapters";
import type { SlackChatPostMessageResult } from "./lib/slackClient";
import { createSlackDestinationAdapter } from "./lib/slackDestinationAdapter";
import { getEffectiveNotificationPreference } from "./query/getEffectiveNotificationPreference.generated";
import { getEventCategoryBinding } from "./query/getEventCategoryBinding.generated";
import { getNotification } from "./query/getNotification.generated";
import { getNotificationChannel } from "./query/getNotificationChannel.generated";
import { getNotificationPreference } from "./query/getNotificationPreference.generated";
import { getNotificationTemplate } from "./query/getNotificationTemplate.generated";
import { getSlackWorkspaceIntegration } from "./query/getSlackWorkspaceIntegration.generated";
import { listAuditByNotification } from "./query/listAuditByNotification.generated";
import { listAuditBySource } from "./query/listAuditBySource.generated";
import { listInboxNotifications } from "./query/listInboxNotifications.generated";
import { listNotificationCategories } from "./query/listNotificationCategories.generated";
import { listNotificationChannels } from "./query/listNotificationChannels.generated";
import { listNotificationPreferences } from "./query/listNotificationPreferences.generated";
import { listNotificationSubscriptions } from "./query/listNotificationSubscriptions.generated";
import { listNotificationTemplates } from "./query/listNotificationTemplates.generated";
import { renderNotificationTemplate } from "./query/renderNotificationTemplate.generated";
import { searchNotificationDeliveryAudit } from "./query/searchNotificationDeliveryAudit.generated";

type UserManagementModule = ReturnType<typeof defineUserManagementModule>;
type UserManagementDB = UserManagementModule["db"];

/**
 * Opt-in Slack destination configuration. When present, {@link defineModule}
 * adds the `SlackWorkspaceIntegration` model, the install/uninstall commands, and
 * the redacted connection query, and auto-wires the Slack `DestinationAdapter`
 * into `adapters.destination` (so the host app does not pass `destination`
 * itself). When omitted, none of the Slack schema or commands are created.
 */
export interface SlackOptions<WF extends Record<string, TailorAnyDBField> = EmptyFields> {
  secretManager: {
    getSecret(vault: string, key: string): Promise<string | undefined>;
  };
  slackWorkspaceIntegration?: CreateSlackWorkspaceIntegrationTypeParams<WF>;
  postChatMessage?: (
    botToken: string,
    channel: string,
    text: string,
  ) => Promise<SlackChatPostMessageResult>;
}

/** Configuration for {@link defineModule}: optional model extensions, the user-management linkage, channel adapters, and executor wiring. */
export interface DefineModuleParams<
  NF extends Record<string, TailorAnyDBField> = EmptyFields,
  PF extends Record<string, TailorAnyDBField> = EmptyFields,
  SF extends Record<string, TailorAnyDBField> = EmptyFields,
  AF extends Record<string, TailorAnyDBField> = EmptyFields,
  CF extends Record<string, TailorAnyDBField> = EmptyFields,
  EvF extends Record<string, TailorAnyDBField> = EmptyFields,
  RedrainCron extends string = string,
  WF extends Record<string, TailorAnyDBField> = EmptyFields,
  S extends SlackOptions<WF> | undefined = undefined,
> {
  notification?: CreateNotificationTypeParams<NF>;
  notificationPreference?: Omit<CreateNotificationPreferenceTypeParams<PF>, "userType">;
  notificationSubscription?: Omit<CreateNotificationSubscriptionTypeParams<SF>, "userType">;
  notificationDeliveryAudit?: CreateNotificationDeliveryAuditTypeParams<AF>;
  channelRoutingBinding?: CreateChannelRoutingBindingTypeParams<CF>;
  notificationEvent?: CreateNotificationEventTypeParams<EvF>;
  userManagement: { db: { user: UserManagementDB["user"] } };
  /**
   * Channel-delivery ports. When the `slack` option is enabled the module
   * supplies `destination` itself, so the host app must not pass it — the type
   * omits `destination` in that case.
   */
  adapters: S extends SlackOptions<WF> ? Omit<DispatchAdapters, "destination"> : DispatchAdapters;
  /** Opt-in Slack destination; when present the module creates the Slack schema/commands and auto-wires the Slack adapter. */
  slack?: S;
  executors: {
    /** Returns the app-side Kysely instance for the shared DB (shared by all executors). */
    getDB: DispatchNotificationEventsParams["getDB"];
    /** CDC drain of newly created NotificationEvents (primary delivery path). */
    dispatchNotificationEvents?: Omit<
      DispatchNotificationEventsParams,
      "notificationEventType" | "adapters" | "getDB"
    >;
    /** Insurance cron re-draining NotificationEvents stuck in PENDING. */
    redrainNotificationEvents: Omit<
      RedrainNotificationEventsParams<RedrainCron>,
      "adapters" | "getDB"
    >;
  };
}

/**
 * Build the optional Slack destination parts: the `SlackWorkspaceIntegration`
 * model, the install/uninstall commands, the redacted connection query, and the
 * concrete Slack {@link createSlackDestinationAdapter} wired against the
 * module's own connection row (read/written through the executor db handle).
 */
function buildSlackParts<WF extends Record<string, TailorAnyDBField>>(
  slack: SlackOptions<WF>,
  getDB: () => DB,
  now: () => Date,
) {
  const slackWorkspaceIntegration = createSlackWorkspaceIntegrationType(
    slack.slackWorkspaceIntegration ?? {},
  );

  const loadConnection = async (): Promise<{ status: string; teamId: string } | null> => {
    const conn = await getDB()
      .selectFrom("SlackWorkspaceIntegration")
      .select(["status", "teamId"])
      .executeTakeFirst();
    return conn ? { status: conn.status, teamId: conn.teamId } : null;
  };

  const destination = createSlackDestinationAdapter({
    loadConnection,
    secretManager: slack.secretManager,
    markConnectionRevoked: async (_reason: string) => {
      await getDB()
        .updateTable("SlackWorkspaceIntegration")
        .set({ status: "REVOKED", revokedAt: now() })
        .where("status", "=", "ACTIVE")
        .execute();
    },
    ...(slack.postChatMessage ? { postChatMessage: slack.postChatMessage } : {}),
  });
  return {
    db: { slackWorkspaceIntegration },
    commands: {
      beginSlackWorkspaceInstall: beginSlackWorkspaceInstall(),
      completeSlackWorkspaceInstall: completeSlackWorkspaceInstall(),
      handleSlackAppUninstalled: handleSlackAppUninstalled(),
    },
    queries: { getSlackWorkspaceIntegration },
    destination,
  };
}

/** Slack parts merged in only when the `slack` option is enabled (type-level gate keyed on `S`). */
type WithSlack<S, WF extends Record<string, TailorAnyDBField>, Part> =
  S extends SlackOptions<WF> ? Part : Record<never, never>;

/** Final {@link defineModule} return shape: core parts always, Slack parts only when enabled. */
type AssembledModule<
  Core extends { db: unknown; commands: unknown; queries: unknown; executors: unknown },
  Slack extends { db: unknown; commands: unknown; queries: unknown },
  S,
  WF extends Record<string, TailorAnyDBField>,
> = {
  db: Core["db"] & WithSlack<S, WF, Slack["db"]>;
  commands: Core["commands"] & WithSlack<S, WF, Slack["commands"]>;
  queries: Core["queries"] & WithSlack<S, WF, Slack["queries"]>;
  executors: Core["executors"];
};

/** Assemble the notification module's DB models, commands, queries, and executors from the host app's configuration. */
/* @__NO_SIDE_EFFECTS__ */
export const defineModule = <
  const NF extends Record<string, TailorAnyDBField> = EmptyFields,
  const PF extends Record<string, TailorAnyDBField> = EmptyFields,
  const SF extends Record<string, TailorAnyDBField> = EmptyFields,
  const AF extends Record<string, TailorAnyDBField> = EmptyFields,
  const CF extends Record<string, TailorAnyDBField> = EmptyFields,
  const EvF extends Record<string, TailorAnyDBField> = EmptyFields,
  const RedrainCron extends string = string,
  const WF extends Record<string, TailorAnyDBField> = EmptyFields,
  const S extends SlackOptions<WF> | undefined = undefined,
>(
  params: DefineModuleParams<NF, PF, SF, AF, CF, EvF, RedrainCron, WF, S>,
) => {
  const now = params.adapters.now ?? (() => new Date());
  const slackParts = params.slack
    ? buildSlackParts(params.slack, params.executors.getDB, now)
    : null;
  // When Slack is enabled the module owns `destination`; otherwise the host's
  // adapters pass through unchanged.
  const adapters = (
    slackParts ? { ...params.adapters, destination: slackParts.destination } : params.adapters
  ) as DispatchAdapters;

  const notification = createNotificationType(params.notification ?? {});
  const notificationPreference = createNotificationPreferenceType({
    ...params.notificationPreference,
    userType: params.userManagement.db.user,
  });
  const notificationSubscription = createNotificationSubscriptionType({
    ...params.notificationSubscription,
    userType: params.userManagement.db.user,
  });
  const notificationDeliveryAudit = createNotificationDeliveryAuditType(
    params.notificationDeliveryAudit ?? {},
  );
  const channelRoutingBinding = createChannelRoutingBindingType(params.channelRoutingBinding ?? {});
  const notificationEvent = createNotificationEventType(params.notificationEvent ?? {});
  const destinationDeliveryLog = createDestinationDeliveryLogType({});

  const executorDeps = {
    getDB: params.executors.getDB,
    adapters,
  };
  const dispatchNotificationEventsExecutor = dispatchNotificationEvents({
    ...params.executors.dispatchNotificationEvents,
    ...executorDeps,
    notificationEventType: notificationEvent,
  });
  const redrainNotificationEventsExecutor = redrainNotificationEvents({
    ...params.executors.redrainNotificationEvents,
    ...executorDeps,
  });

  const core = {
    db: {
      channelRoutingBinding,
      destinationDeliveryLog,
      eventCategoryBinding,
      notification,
      notificationEvent,
      notificationCategory,
      notificationChannel,
      notificationDeliveryAudit,
      notificationPreference,
      notificationSubscription,
      notificationTemplate,
    },
    commands: {
      activateNotificationChannel: activateNotificationChannel(),
      anonymizeNotificationsForUser: anonymizeNotificationsForUser(),
      archiveNotification: archiveNotification(),
      bulkUpdateNotificationPreferences: bulkUpdateNotificationPreferences(),
      createNotificationChannel: createNotificationChannel(),
      createNotificationSubscription: createNotificationSubscription(),
      createNotificationTemplate: createNotificationTemplate(),
      deactivateNotificationChannel: deactivateNotificationChannel(),
      deleteNotificationSubscription: deleteNotificationSubscription(),
      deleteNotificationTemplate: deleteNotificationTemplate(),
      logNotificationEvent: logNotificationEvent(),
      markAllNotificationsAsRead: markAllNotificationsAsRead(),
      markNotificationAsRead: markNotificationAsRead(),
      markNotificationsAsSeen: markNotificationsAsSeen(),
      recordDeliveryEvent: recordDeliveryEvent(),
      resetNotificationPreferences: resetNotificationPreferences(),
      runNotificationAuditRetentionSweep: runNotificationAuditRetentionSweep(),
      updateNotificationChannel: updateNotificationChannel(),
      updateNotificationPreference: updateNotificationPreference(),
      updateNotificationTemplate: updateNotificationTemplate(),
    },
    queries: {
      getEffectiveNotificationPreference,
      getEventCategoryBinding,
      getNotification,
      getNotificationChannel,
      getNotificationPreference,
      getNotificationTemplate,
      listAuditByNotification,
      listAuditBySource,
      listInboxNotifications,
      listNotificationCategories,
      listNotificationChannels,
      listNotificationPreferences,
      listNotificationSubscriptions,
      listNotificationTemplates,
      renderNotificationTemplate,
      searchNotificationDeliveryAudit,
    },
    executors: {
      dispatchNotificationEvents: dispatchNotificationEventsExecutor,
      redrainNotificationEvents: redrainNotificationEventsExecutor,
    },
  };

  return {
    db: { ...core.db, ...slackParts?.db },
    commands: { ...core.commands, ...slackParts?.commands },
    queries: { ...core.queries, ...slackParts?.queries },
    executors: core.executors,
  } as AssembledModule<typeof core, NonNullable<typeof slackParts>, S, WF>;
};
