import { renderTemplate } from './renderer.js';
import { sendEmail } from './channels/email.js';
import { sendSlackMessage } from './channels/slack.js';
import { sendTelegramMessage } from './channels/telegram.js';
import * as repo from './repository.js';
import type {
  DeliveryChannel,
  DeliveryLogDoc,
  EmailChannelConfig,
  SlackChannelConfig,
  TelegramChannelConfig,
} from './types.js';

// ── Delivery Dispatcher ──────────────────────────────────────
// Routes delivery requests to the correct channel adapter.
// Logs all deliveries to Cosmos for audit and debugging.

/**
 * Resolve email config from environment. Defaults to console provider for dev.
 */
export function resolveEmailConfig(): EmailChannelConfig {
  const provider = (process.env.EMAIL_PROVIDER || 'console') as EmailChannelConfig['provider'];
  return {
    provider,
    apiKey: process.env.EMAIL_API_KEY,
    fromEmail: process.env.EMAIL_FROM_ADDRESS || 'noreply@bytelyst.com',
    fromName: process.env.EMAIL_FROM_NAME || 'ByteLyst',
    smtpHost: process.env.SMTP_HOST,
    smtpPort: process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : undefined,
    smtpSecure: process.env.SMTP_SECURE ? process.env.SMTP_SECURE === 'true' : undefined,
    smtpUser: process.env.SMTP_USER,
    smtpPassword: process.env.SMTP_PASSWORD,
  };
}

export function resolveTelegramConfig(): TelegramChannelConfig {
  return {
    botToken: process.env.TELEGRAM_BOT_TOKEN,
    defaultChatId: process.env.TELEGRAM_DEFAULT_CHAT_ID,
  };
}

export function resolveSlackConfig(): SlackChannelConfig {
  return {
    webhookUrl: process.env.SLACK_WEBHOOK_URL,
    defaultChannel: process.env.SLACK_DEFAULT_CHANNEL,
  };
}

function buildDeliveryPk(productId: string, channel: DeliveryChannel, now: Date): string {
  return `${productId}:${channel}:${now.getUTCFullYear()}${String(now.getUTCMonth() + 1).padStart(2, '0')}`;
}

async function createPendingLog(doc: DeliveryLogDoc): Promise<void> {
  try {
    await repo.createDeliveryLog(doc);
  } catch {
    // Non-fatal
  }
}

async function finalizeLog(
  logDoc: DeliveryLogDoc,
  result: { success: boolean; messageId?: string; error?: string }
): Promise<void> {
  try {
    await repo.updateDeliveryLog({
      ...logDoc,
      status: result.success ? 'sent' : 'failed',
      sentAt: result.success ? new Date().toISOString() : undefined,
      error: result.error,
      metadata: result.messageId ? { messageId: result.messageId } : undefined,
    });
  } catch {
    // Non-fatal
  }
}

/**
 * Send a templated email. Renders the template, sends via configured channel,
 * and logs the delivery attempt.
 */
export async function dispatchEmail(
  options: {
    to: string;
    templateId: string;
    variables: Record<string, string>;
    productId: string;
    userId?: string;
  },
  log: { info: (...a: unknown[]) => void; error: (...a: unknown[]) => void }
): Promise<{ success: boolean; messageId?: string; error?: string }> {
  const config = resolveEmailConfig();
  const now = new Date();
  const pk = buildDeliveryPk(options.productId, 'email', now);

  // Render template
  let rendered: { subject: string; bodyHtml: string; bodyText: string };
  try {
    rendered = renderTemplate(options.templateId, options.variables);
  } catch (err: unknown) {
    const error = err instanceof Error ? err.message : String(err);
    log.error({ templateId: options.templateId, error }, '[delivery] Template render failed');
    return { success: false, error };
  }

  // Create pending log entry
  const logDoc: DeliveryLogDoc = {
    id: `del_${crypto.randomUUID()}`,
    productId: options.productId,
    pk,
    userId: options.userId,
    channel: 'email',
    templateId: options.templateId,
    to: options.to,
    subject: rendered.subject,
    status: 'pending',
    createdAt: now.toISOString(),
  };

  await createPendingLog(logDoc);

  // Send
  const result = await sendEmail(
    {
      to: options.to,
      from: config.fromEmail,
      fromName: config.fromName,
      subject: rendered.subject,
      bodyHtml: rendered.bodyHtml,
      bodyText: rendered.bodyText,
    },
    config,
    log
  );

  // Update log
  await finalizeLog(logDoc, result);

  return result;
}

export async function dispatchTelegram(options: {
  text: string;
  productId: string;
  chatId?: string;
  disableNotification?: boolean;
  userId?: string;
}): Promise<{ success: boolean; messageId?: string; error?: string }> {
  const config = resolveTelegramConfig();
  const chatId = options.chatId || config.defaultChatId;
  if (!chatId) {
    return { success: false, error: 'Telegram chat ID not configured' };
  }

  const now = new Date();
  const logDoc: DeliveryLogDoc = {
    id: `del_${crypto.randomUUID()}`,
    productId: options.productId,
    pk: buildDeliveryPk(options.productId, 'telegram', now),
    userId: options.userId,
    channel: 'telegram',
    to: chatId,
    subject: 'Telegram notification',
    status: 'pending',
    createdAt: now.toISOString(),
  };

  await createPendingLog(logDoc);
  const result = await sendTelegramMessage(
    {
      chatId,
      text: options.text,
      disableNotification: options.disableNotification,
    },
    config
  );
  await finalizeLog(logDoc, result);
  return result;
}

export async function dispatchSlack(options: {
  text: string;
  productId: string;
  channel?: string;
  username?: string;
  userId?: string;
}): Promise<{ success: boolean; messageId?: string; error?: string }> {
  const config = resolveSlackConfig();
  const destination = options.channel || config.defaultChannel || 'configured-webhook';
  const now = new Date();
  const logDoc: DeliveryLogDoc = {
    id: `del_${crypto.randomUUID()}`,
    productId: options.productId,
    pk: buildDeliveryPk(options.productId, 'slack', now),
    userId: options.userId,
    channel: 'slack',
    to: destination,
    subject: 'Slack notification',
    status: 'pending',
    createdAt: now.toISOString(),
  };

  await createPendingLog(logDoc);
  const result = await sendSlackMessage(
    {
      text: options.text,
      channel: options.channel,
      username: options.username,
    },
    config
  );
  await finalizeLog(logDoc, result);
  return result;
}
