/**
 * Publishing Orchestrator - Multi-Channel Content Delivery
 * Deep Agents 3.0 - Autonomous content delivery with format adaptation
 *
 * Orchestrates:
 * - Channel-specific formatting
 * - Parallel delivery to multiple channels
 * - Retry handling and failure recovery
 * - Delivery tracking and analytics
 */

import { v } from "convex/values";
import {
  internalAction,
  internalMutation,
  internalQuery,
} from "../../../_generated/server";
import { internal } from "../../../_generated/api";
import { PUBLISHING_CONFIG } from "../../../config/autonomousConfig";
import type { Doc, Id } from "../../../_generated/dataModel";

/* ================================================================== */
/* TYPES                                                               */
/* ================================================================== */

export interface FormattedContent {
  channel: string;
  payload: unknown;
}

export interface DeliveryResult {
  channel: string;
  success: boolean;
  deliveredAt?: number;
  messageId?: string;
  error?: string;
  retryCount?: number;
}

interface NtfyPayload {
  topic: string;
  title: string;
  message: string;
  priority: number;
  tags?: string[];
  click?: string;
}

interface EmailPayload {
  to: string[];
  subject: string;
  html: string;
  text?: string;
}

/* ================================================================== */
/* FORMATTERS                                                          */
/* ================================================================== */

/**
 * Format content for ntfy push notification
 */
function formatForNtfy(
  content: Doc<"publishingTasks">["content"],
  config: Doc<"publishingTasks">["channels"][0]
): NtfyPayload {
  const { maxTitle, maxMessage, actIIIReserve } = PUBLISHING_CONFIG.formatBudgets.ntfy;

  // Guarantee ACT III visibility (next actions)
  const actIII = content.nextActions.slice(0, 3).join(" | ");
  const actIIIBudget = Math.min(actIII.length + 10, actIIIReserve);

  const remainingBudget = maxMessage - actIIIBudget;
  const summary = content.summary.slice(0, remainingBudget);

  // Map urgency to ntfy priority (1-5)
  let priority = 3;
  switch (config.urgency) {
    case "critical":
      priority = 5;
      break;
    case "high":
      priority = 4;
      break;
    case "medium":
      priority = 3;
      break;
    case "low":
      priority = 2;
      break;
  }

  // Derive emoji tags from key facts
  const tags: string[] = [];
  for (const fact of content.keyFacts.slice(0, 3)) {
    if (fact.category === "funding") tags.push("money_with_wings");
    if (fact.category === "news") tags.push("newspaper");
    if (fact.category === "contact") tags.push("telephone");
    if (fact.category === "metric") tags.push("chart_with_upwards_trend");
  }

  return {
    topic: process.env.NTFY_TOPIC || "nodebench",
    title: (content.keyFacts[0]?.label || content.persona).slice(0, maxTitle),
    message: `${summary}\n\n📌 ${actIII}`,
    priority,
    tags: tags.length > 0 ? tags : ["robot"],
    click: `${process.env.APP_URL || "https://nodebench.ai"}/entity/${content.keyFacts[0]?.value || ""}`,
  };
}

/**
 * Format content for email
 */
function formatForEmail(
  content: Doc<"publishingTasks">["content"],
  config: Doc<"publishingTasks">["channels"][0],
  entityName?: string
): EmailPayload {
  const { maxSubject, maxSummary } = PUBLISHING_CONFIG.formatBudgets.email;

  const subject = `[NodeBench] ${entityName || "Research"}: ${content.summary.slice(0, maxSubject - 30)}...`;

  // Build HTML email
  const keyFactsHtml = content.keyFacts
    .map(
      (fact: { label: string; value: string; category?: string }) =>
        `<tr><td style="padding:8px;border-bottom:1px solid #eee;"><strong>${fact.label}</strong></td><td style="padding:8px;border-bottom:1px solid #eee;">${fact.value}</td></tr>`
    )
    .join("");

  const nextActionsHtml = content.nextActions
    .map((action: string) => `<li style="margin:4px 0;">${action}</li>`)
    .join("");

  const html = `
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="font-family:system-ui,-apple-system,sans-serif;max-width:600px;margin:0 auto;padding:20px;background:#f9fafb;">
  <div style="background:white;border-radius:8px;padding:24px;box-shadow:0 1px 3px rgba(0,0,0,0.1);">
    <!-- Header -->
    <div style="border-bottom:2px solid #3b82f6;padding-bottom:16px;margin-bottom:20px;">
      <h1 style="margin:0;color:#1f2937;font-size:20px;">${entityName || "Research Brief"}</h1>
      <p style="margin:4px 0 0;color:#6b7280;font-size:12px;">Generated by ${content.persona} • ${new Date().toLocaleDateString()}</p>
    </div>

    <!-- Summary -->
    <div style="background:#f0f9ff;border-left:4px solid #3b82f6;padding:12px;margin-bottom:20px;">
      <p style="margin:0;color:#1e40af;font-size:14px;">${content.summary.slice(0, maxSummary)}</p>
    </div>

    <!-- Key Facts -->
    <h2 style="color:#374151;font-size:16px;margin:20px 0 12px;">Key Facts</h2>
    <table style="width:100%;border-collapse:collapse;">
      ${keyFactsHtml}
    </table>

    <!-- Next Actions -->
    <h2 style="color:#374151;font-size:16px;margin:24px 0 12px;">📌 Next Actions</h2>
    <ul style="margin:0;padding-left:20px;color:#4b5563;">
      ${nextActionsHtml}
    </ul>

    <!-- Footer -->
    <div style="margin-top:24px;padding-top:16px;border-top:1px solid #e5e7eb;text-align:center;">
      <p style="margin:0;color:#9ca3af;font-size:11px;">
        Generated by NodeBench Autonomous Agent Ecosystem<br>
        <a href="${process.env.APP_URL || "https://nodebench.ai"}/preferences" style="color:#3b82f6;">Manage preferences</a>
      </p>
    </div>
  </div>
</body>
</html>`;

  return {
    to: config.recipients || [],
    subject,
    html,
    text: `${content.summary}\n\nKey Facts:\n${content.keyFacts.map((f: { label: string; value: string }) => `- ${f.label}: ${f.value}`).join("\n")}\n\nNext Actions:\n${content.nextActions.map((a: string) => `- ${a}`).join("\n")}`,
  };
}

/**
 * Format content for UI (full research output)
 */
function formatForUI(
  content: Doc<"publishingTasks">["content"],
  _config: Doc<"publishingTasks">["channels"][0]
): object {
  return {
    type: "research_result",
    persona: content.persona,
    summary: content.summary,
    keyFacts: content.keyFacts,
    nextActions: content.nextActions,
    raw: content.raw,
    timestamp: Date.now(),
  };
}

/**
 * Format content for the appropriate channel
 */
function formatForChannel(
  content: Doc<"publishingTasks">["content"],
  config: Doc<"publishingTasks">["channels"][0],
  entityName?: string
): FormattedContent {
  switch (config.channel) {
    case "ntfy":
      return { channel: "ntfy", payload: formatForNtfy(content, config) };
    case "email":
      return { channel: "email", payload: formatForEmail(content, config, entityName) };
    case "ui":
      return { channel: "ui", payload: formatForUI(content, config) };
    default:
      return { channel: config.channel, payload: { content, config } };
  }
}

/* ================================================================== */
/* QUERIES                                                             */
/* ================================================================== */

/**
 * Get a publishing task by ID
 */
export const getPublishingTask = internalQuery({
  args: { publishingTaskId: v.id("publishingTasks") },
  handler: async (ctx, { publishingTaskId }): Promise<Doc<"publishingTasks"> | null> => {
    return (await ctx.db.get(publishingTaskId)) as Doc<"publishingTasks"> | null;
  },
});

/**
 * Get pending publishing tasks
 */
export const getPendingTasks = internalQuery({
  args: { limit: v.optional(v.number()) },
  handler: async (ctx, { limit = 50 }): Promise<Doc<"publishingTasks">[]> => {
    return (await ctx.db
      .query("publishingTasks")
      .withIndex("by_status", (q) => q.eq("status", "pending"))
      .order("asc")
      .take(limit)) as Doc<"publishingTasks">[];
  },
});

export const getPublishingStats = internalQuery({
  args: {},
  handler: async (ctx): Promise<{
    pending: number;
    publishedToday: number;
    failedToday: number;
  }> => {
    const tasks = await ctx.db.query("publishingTasks").collect() as Doc<"publishingTasks">[];
    const startOfDay = new Date();
    startOfDay.setHours(0, 0, 0, 0);
    const startOfDayMs = startOfDay.getTime();

    let pending = 0;
    let publishedToday = 0;
    let failedToday = 0;
    for (const task of tasks) {
      if (task.status === "pending" || task.status === "formatting" || task.status === "delivering") {
        pending++;
      }
      if ((task.status === "completed" || task.status === "partial") && (task.completedAt ?? 0) >= startOfDayMs) {
        publishedToday++;
      }
      if (task.status === "failed" && (task.completedAt ?? task.createdAt) >= startOfDayMs) {
        failedToday++;
      }
    }

    return { pending, publishedToday, failedToday };
  },
});

/* ================================================================== */
/* MUTATIONS                                                           */
/* ================================================================== */

/**
 * Update publishing task status
 */
export const updateTaskStatus = internalMutation({
  args: {
    publishingTaskId: v.id("publishingTasks"),
    status: v.union(
      v.literal("pending"),
      v.literal("formatting"),
      v.literal("delivering"),
      v.literal("completed"),
      v.literal("partial"),
      v.literal("failed")
    ),
    deliveryResults: v.optional(
      v.array(
        v.object({
          channel: v.string(),
          success: v.boolean(),
          deliveredAt: v.optional(v.number()),
          messageId: v.optional(v.string()),
          error: v.optional(v.string()),
          retryCount: v.optional(v.number()),
        })
      )
    ),
  },
  handler: async (ctx, { publishingTaskId, status, deliveryResults }): Promise<void> => {
    const updates: Partial<Doc<"publishingTasks">> = { status };

    if (status === "formatting") {
      updates.formattedAt = Date.now();
    }
    if (status === "completed" || status === "partial" || status === "failed") {
      updates.completedAt = Date.now();
    }
    if (deliveryResults) {
      updates.deliveryResults = deliveryResults;
    }

    await ctx.db.patch(publishingTaskId, updates);
  },
});

/**
 * Create a delivery job
 */
export const createDeliveryJob = internalMutation({
  args: {
    channel: v.string(),
    recipient: v.optional(v.string()),
    payload: v.any(),
    publishingTaskId: v.optional(v.id("publishingTasks")),
  },
  handler: async (ctx, args): Promise<Id<"deliveryJobs">> => {
    return await ctx.db.insert("deliveryJobs", {
      channel: args.channel,
      recipient: args.recipient,
      payload: args.payload,
      publishingTaskId: args.publishingTaskId,
      status: "pending",
      attempts: 0,
      maxAttempts: PUBLISHING_CONFIG.maxDeliveryAttempts,
      createdAt: Date.now(),
    });
  },
});

/* ================================================================== */
/* ACTIONS                                                             */
/* ================================================================== */

/**
 * Deliver to ntfy
 */
async function deliverToNtfy(payload: NtfyPayload): Promise<{ success: boolean; messageId?: string; error?: string }> {
  try {
    const ntfyUrl = process.env.NTFY_URL || "https://ntfy.sh";
    const response = await fetch(`${ntfyUrl}/${payload.topic}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: payload.title,
        message: payload.message,
        priority: payload.priority,
        tags: payload.tags,
        click: payload.click,
      }),
    });

    if (!response.ok) {
      return { success: false, error: `HTTP ${response.status}: ${await response.text()}` };
    }

    return { success: true, messageId: `ntfy-${Date.now()}` };
  } catch (error) {
    return { success: false, error: error instanceof Error ? error.message : String(error) };
  }
}

/**
 * Deliver to a specific channel
 */
async function deliverToChannel(
  channel: string,
  payload: unknown
): Promise<{ success: boolean; messageId?: string; error?: string }> {
  switch (channel) {
    case "ntfy":
      return deliverToNtfy(payload as NtfyPayload);
    case "email":
      // TODO: Integrate with Resend
      console.log("[PublishingOrchestrator] Email delivery not implemented yet");
      return { success: true, messageId: `email-${Date.now()}` };
    case "ui":
      // UI delivery is handled via Convex subscriptions
      return { success: true, messageId: `ui-${Date.now()}` };
    default:
      return { success: false, error: `Unknown channel: ${channel}` };
  }
}

/**
 * Process a single publishing task
 */
export const processPublishingTask = internalAction({
  args: { publishingTaskId: v.id("publishingTasks") },
  handler: async (ctx, { publishingTaskId }): Promise<void> => {
    console.log(`[PublishingOrchestrator] Processing task ${publishingTaskId}`);

    // 1. Get the task
    const task = await ctx.runQuery(
      internal.domains.publishing.publishingOrchestrator.getPublishingTask,
      { publishingTaskId }
    );

    if (!task) {
      console.error(`[PublishingOrchestrator] Task not found: ${publishingTaskId}`);
      return;
    }

    if (task.status !== "pending") {
      console.log(`[PublishingOrchestrator] Task ${publishingTaskId} already processed`);
      return;
    }

    // 2. Mark as formatting
    await ctx.runMutation(
      internal.domains.publishing.publishingOrchestrator.updateTaskStatus,
      { publishingTaskId, status: "formatting" }
    );

    try {
      // 3. Format content for each enabled channel
      type ChannelConfig = Doc<"publishingTasks">["channels"][0];
      const enabledChannels = task.channels.filter((c: ChannelConfig) => c.enabled);
      const formattedContents = enabledChannels.map((channelConfig: ChannelConfig) =>
        formatForChannel(task.content, channelConfig, task.entityName)
      );

      // 4. Mark as delivering
      await ctx.runMutation(
        internal.domains.publishing.publishingOrchestrator.updateTaskStatus,
        { publishingTaskId, status: "delivering" }
      );

      // 5. Deliver to each channel in parallel
      const deliveryResults: DeliveryResult[] = await Promise.all(
        formattedContents.map(async (formatted: FormattedContent) => {
          const result = await deliverToChannel(formatted.channel, formatted.payload);
          return {
            channel: formatted.channel,
            success: result.success,
            deliveredAt: result.success ? Date.now() : undefined,
            messageId: result.messageId,
            error: result.error,
            retryCount: 0,
          };
        })
      );

      // 6. Determine final status
      const successCount = deliveryResults.filter((r) => r.success).length;
      const totalCount = deliveryResults.length;

      let finalStatus: "completed" | "partial" | "failed";
      if (successCount === totalCount) {
        finalStatus = "completed";
      } else if (successCount > 0) {
        finalStatus = "partial";
      } else {
        finalStatus = "failed";
      }

      // 7. Update task with results
      await ctx.runMutation(
        internal.domains.publishing.publishingOrchestrator.updateTaskStatus,
        { publishingTaskId, status: finalStatus, deliveryResults }
      );

      console.log(
        `[PublishingOrchestrator] Task ${publishingTaskId} ${finalStatus}: ${successCount}/${totalCount} deliveries`
      );

      // 8. Create delivery jobs for failed channels (for retry)
      for (const result of deliveryResults) {
        if (!result.success) {
          const formattedContent = formattedContents.find((f: FormattedContent) => f.channel === result.channel);
          if (formattedContent) {
            await ctx.runMutation(
              internal.domains.publishing.publishingOrchestrator.createDeliveryJob,
              {
                channel: result.channel,
                payload: formattedContent.payload,
                publishingTaskId,
              }
            );
          }
        }
      }
    } catch (error) {
      console.error(`[PublishingOrchestrator] Error processing task ${publishingTaskId}:`, error);

      await ctx.runMutation(
        internal.domains.publishing.publishingOrchestrator.updateTaskStatus,
        { publishingTaskId, status: "failed" }
      );
    }
  },
});

/**
 * Process pending publishing tasks
 */
export const processPendingTasks = internalAction({
  args: { limit: v.optional(v.number()) },
  handler: async (ctx, { limit = 10 }): Promise<number> => {
    const pendingTasks = await ctx.runQuery(
      internal.domains.publishing.publishingOrchestrator.getPendingTasks,
      { limit }
    );

    if (pendingTasks.length === 0) {
      return 0;
    }

    let processed = 0;
    for (const task of pendingTasks) {
      try {
        await ctx.runAction(
          internal.domains.publishing.publishingOrchestrator.processPublishingTask,
          { publishingTaskId: task._id }
        );
        processed++;
      } catch (error) {
        console.error(`[PublishingOrchestrator] Failed to process task ${task._id}:`, error);
      }
    }

    return processed;
  },
});

/**
 * Main tick function - called by cron
 */
export const tickPublishing = internalAction({
  args: {},
  handler: async (ctx): Promise<void> => {
    console.log("[PublishingOrchestrator] Starting publishing tick...");

    const processed = await ctx.runAction(
      internal.domains.publishing.publishingOrchestrator.processPendingTasks,
      {}
    );

    console.log(`[PublishingOrchestrator] Tick complete. Processed ${processed} tasks.`);
  },
});
