import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import type { Transaction } from "../generated/kysely-tailordb";
import { DuplicateChannelIdError, MissingRequiredFieldError } from "../lib/errors.generated";

export type ChannelId = "IN_APP" | "EMAIL" | "SMS" | "PUSH" | "SLACK" | "TEAMS";

export type ChannelKind = "PERSONAL" | "DESTINATION";

// Channels that post to a shared surface (one post per ChannelRoutingBinding)
// rather than fanning out per recipient.
const DESTINATION_CHANNEL_IDS: readonly ChannelId[] = ["SLACK", "TEAMS"];

export function defaultKindForChannel(channelId: ChannelId): ChannelKind {
  return DESTINATION_CHANNEL_IDS.includes(channelId) ? "DESTINATION" : "PERSONAL";
}

export interface ChannelCapabilities {
  supportsHtmlBody: boolean;
  supportsAttachments: boolean;
  supportsRichActions: boolean;
}

export interface CreateNotificationChannelInput {
  channelId: ChannelId;
  // `kind` is intentionally not a caller input — it is derived from `channelId`
  // at insert time via the fixed mapping in defaultKindForChannel().
  displayName: string;
  capabilities: ChannelCapabilities;
  enabled?: boolean;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object";
}

function hasAllCapabilityKeys(value: unknown): value is ChannelCapabilities {
  if (!isRecord(value)) return false;
  return (
    typeof value.supportsHtmlBody === "boolean" &&
    typeof value.supportsAttachments === "boolean" &&
    typeof value.supportsRichActions === "boolean"
  );
}

/**
 * Inserts a row into the NotificationChannel registry so the dispatcher can
 * target a new delivery medium. The `kind` topology is derived from `channelId`
 * (never caller-supplied); `enabled` defaults to `true`. Rejects a duplicate
 * `channelId` and missing/blank required fields.
 */
export async function run(
  db: Transaction,
  input: CreateNotificationChannelInput,
  _ctx: CommandContext,
) {
  const { channelId, displayName, capabilities, enabled } = input;

  if (!channelId) {
    return err(new MissingRequiredFieldError("channelId"));
  }

  const trimmedDisplayName = displayName?.trim() ?? "";
  if (trimmedDisplayName === "") {
    return err(new MissingRequiredFieldError("displayName"));
  }

  if (!hasAllCapabilityKeys(capabilities)) {
    return err(new MissingRequiredFieldError("capabilities"));
  }

  const existing = await db
    .selectFrom("NotificationChannel")
    .select("id")
    .where("channelId", "=", channelId)
    .executeTakeFirst();

  if (existing) {
    return err(new DuplicateChannelIdError(channelId));
  }

  const now = new Date();
  const channel = await db
    .insertInto("NotificationChannel")
    .values({
      id: crypto.randomUUID(),
      channelId,
      kind: defaultKindForChannel(channelId),
      displayName: trimmedDisplayName,
      capabilities,
      enabled: enabled ?? true,
      createdAt: now,
      updatedAt: now,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ channel });
}
