import type { DatabaseClient } from "./database";
import { shanghaiDateFromTimestamp } from "./collection-service";
import {
  sendWecomAppMessage,
  sendWecomRobotMessage,
  type WecomConfig,
} from "./wecom-client";
import { getRuntimeEnv } from "./runtime-env";

type FetchLike = typeof fetch;

export type WecomNotifySummary = {
  dueSoonAttempted: number;
  dueSoonSent: number;
  dueSoonFailed: number;
  dueSoonSkipped: number;
  digestSent: boolean;
};

type DueSoonRow = {
  distribution_id: string;
  partner_id: string;
  partner_name: string;
  wecom_external_user_id: string | null;
  task_name: string;
  due_at: string;
  content_title: string;
};

export function computeDueCutoff(today: string, dueDays: number) {
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(today);
  if (!match) return today;
  const [, y, m, d] = match;
  const date = new Date(
    Date.UTC(Number(y), Number(m) - 1, Number(d)) + dueDays * 24 * 60 * 60 * 1_000,
  );
  return date.toISOString().slice(0, 10);
}

export async function runDueSoonWecomNotifications(
  db: DatabaseClient,
  config: WecomConfig,
  fetchImpl: FetchLike = fetch,
  now: number = Date.now(),
): Promise<WecomNotifySummary> {
  const today = shanghaiDateFromTimestamp(now);
  const cutoff = computeDueCutoff(today, config.dueDays);
  const portalUrl = bindingValue(getRuntimeEnv().KOC_PORTAL_URL);

  const result = await db
    .prepare(
      `SELECT
        d.id AS distribution_id,
        d.partner_id,
        p.name AS partner_name,
        p.wecom_external_user_id,
        t.name AS task_name,
        t.due_at,
        c.title AS content_title
       FROM distributions d
       JOIN tasks t ON t.id = d.task_id
       JOIN partners p ON p.id = d.partner_id
       JOIN contents c ON c.id = d.content_id
       WHERE (d.publish_url IS NULL OR d.publish_url = '')
         AND t.due_at IS NOT NULL AND t.due_at != ''
         AND t.due_at <= ?
       ORDER BY t.due_at ASC, p.name ASC`,
    )
    .bind(cutoff)
    .all<DueSoonRow>();

  const grouped = new Map<
    string,
    {
      partnerName: string;
      externalUserId: string | null;
      rows: DueSoonRow[];
    }
  >();
  for (const row of result.results) {
    const entry = grouped.get(row.partner_id) ?? {
      partnerName: row.partner_name,
      externalUserId: row.wecom_external_user_id,
      rows: [],
    };
    entry.rows.push(row);
    if (!entry.externalUserId && row.wecom_external_user_id) {
      entry.externalUserId = row.wecom_external_user_id;
    }
    grouped.set(row.partner_id, entry);
  }

  let dueSoonAttempted = 0;
  let dueSoonSent = 0;
  let dueSoonFailed = 0;
  let dueSoonSkipped = 0;
  const digestTasks: string[] = [];

  for (const [, entry] of grouped) {
    dueSoonAttempted += 1;
    const external = entry.externalUserId
      ? [entry.externalUserId]
      : [];
    const lines = entry.rows.slice(0, 5).map((row) => {
      return `· 《${truncate(row.task_name, 24)}》— ${truncate(row.content_title, 24)}（截止 ${row.due_at}）`;
    });
    const overflow =
      entry.rows.length > 5 ? `\n…还有 ${entry.rows.length - 5} 条` : "";
    const link = portalUrl ? `\n领取链接：${portalUrl}` : "";
    const content =
      `${entry.partnerName}，你有 ${entry.rows.length} 条内容待发布：\n${lines.join("\n")}${overflow}${link}`;
    const appResult = await sendWecomAppMessage(
      external,
      content,
      config,
      fetchImpl,
    ).catch((error: unknown) => {
      console.warn(
        "[KOC LOOP] wecom app message failed",
        { partner: entry.partnerName, error: safeError(error) },
      );
      return null;
    });
    if (appResult === null) {
      dueSoonFailed += 1;
    } else if (appResult.skipped) {
      dueSoonSkipped += 1;
    } else {
      dueSoonSent += 1;
    }
    const earliestDue = entry.rows[0]?.due_at ?? "";
    digestTasks.push(
      `· ${entry.partnerName}（${entry.rows.length} 条，最近截止 ${earliestDue}）`,
    );
  }

  let digestSent = false;
  if (config.robotWebhook && digestTasks.length > 0) {
    const digest =
      `今日待发布催办（${today}，截止 ≤ ${cutoff}）：\n${digestTasks.join("\n")}`;
    try {
      await sendWecomRobotMessage(digest, config, fetchImpl);
      digestSent = true;
    } catch (error) {
      console.warn(
        "[KOC LOOP] wecom robot digest failed",
        { error: safeError(error) },
      );
    }
  }

  return {
    dueSoonAttempted,
    dueSoonSent,
    dueSoonFailed,
    dueSoonSkipped,
    digestSent,
  };
}

function bindingValue(value: unknown) {
  return String(value ?? "").trim();
}

function truncate(value: string, max: number) {
  return value.length > max ? `${value.slice(0, max)}…` : value;
}

function safeError(error: unknown) {
  return error instanceof Error ? error.message : String(error);
}
