import { readAutoPostLogs } from "@/lib/logging/auto-post-logger";
import { readPublishLogs } from "@/lib/logging/publish-logs";
import type {
  AutoPostMonitorPlatform,
  AutoPostMonitorSnapshot,
} from "@/types/auto-post-monitor";
import { getAutoPostLockState, isAutoPostRunInProgress } from "./auto-post-lock";
import { evaluateAutoPostDiagnostics } from "./auto-post-diagnostics";
import { evaluateSchedulerHealth } from "./scheduler-health";
import {
  computeNextScheduleSlot,
  formatDuration,
  formatLocalDateTime,
  SLOT_WINDOW_MINUTES,
} from "./next-slot";
import { parseProjectTimezone } from "./plan-times";
import { resolveScheduleSeed } from "./resolve-schedule";

export type { AutoPostMonitorPlatform, AutoPostMonitorSnapshot } from "@/types/auto-post-monitor";

const ATTEMPT_EVENTS = new Set([
  "auto_post_started",
  "auto_post_tick_completed",
  "outside_schedule_slot",
  "slot_processing_started",
  "proposal_posted",
  "publish_started",
  "publish_failed",
  "publish_blocked",
  "oauth_connect_started",
  "oauth_callback_received",
  "oauth_connect_success",
  "oauth_connect_failed",
]);

function platformLabel(id: string): string {
  const map: Record<string, string> = {
    linkedin: "LinkedIn",
    facebook: "Facebook",
    instagram: "Instagram",
    youtube: "YouTube",
  };
  return map[id] ?? id;
}

function platformStatusTone(
  connected: boolean,
  ready: boolean,
  connectionStatus: string,
): { label: string; tone: AutoPostMonitorPlatform["statusTone"] } {
  if (!connected || connectionStatus === "disconnected") {
    return { label: "Not connected", tone: "muted" };
  }
  if (ready) {
    return { label: "Connected", tone: "ok" };
  }
  if (connectionStatus.includes("permission") || connectionStatus.includes("scope")) {
    return { label: "Permission required", tone: "warn" };
  }
  if (connectionStatus === "expired" || connectionStatus === "error") {
    return { label: "Reconnect required", tone: "error" };
  }
  return { label: "Connected — not publish-ready", tone: "warn" };
}

function attemptResultTone(
  event: string,
  level?: string,
): "ok" | "warn" | "error" | "muted" {
  if (level === "ERROR") return "error";
  if (event === "proposal_posted" || event === "publish_started" || level === "SUCCESS") return "ok";
  if (event === "outside_schedule_slot" || event === "auto_post_tick_completed") return "muted";
  return "warn";
}

function attemptResultLabel(event: string, message: string): string {
  if (event === "outside_schedule_slot") return "Outside schedule slot";
  if (event === "proposal_posted") return "Post published";
  if (event === "publish_failed" || event === "publish_blocked") return "Publish failed";
  if (event === "oauth_connect_failed") return "OAuth failed";
  if (event === "oauth_connect_success") return "OAuth connected";
  if (event === "auto_post_tick_completed") {
    return message.includes("0 slot") ? "No slot action" : "Tick completed";
  }
  return message;
}

/** Datetime/OAuth setup errors already fixed in app — hide from Live Generation red box. */
function isStaleSetupError(message: string): boolean {
  const m = message.toLowerCase();
  if (m.includes("incorrect datetime value")) return true;
  if (m.includes("last_synced_at") && m.includes("datetime")) return true;
  if (m.includes("token_expires_at") && m.includes("datetime")) return true;
  if (m.includes("is not saved on the server yet")) return true;
  return false;
}

function pickActiveLastError(input: {
  errorAutoLog?: {
    timestamp: string;
    message: string;
    errorMessage?: string;
    event: string;
    providerId?: string;
  } | null;
  errorPublishLog?: {
    timestamp: string;
    message: string;
    platform?: string;
  } | null;
  oauthError?: {
    timestamp: string;
    message: string;
    providerId?: string;
    level?: string;
  } | null;
}): AutoPostMonitorSnapshot["lastError"] {
  const candidates: Array<NonNullable<AutoPostMonitorSnapshot["lastError"]>> = [];

  if (input.errorAutoLog) {
    const message = input.errorAutoLog.errorMessage ?? input.errorAutoLog.message;
    if (!isStaleSetupError(message)) {
      candidates.push({
        timestamp: input.errorAutoLog.timestamp,
        message,
        source: input.errorAutoLog.event.includes("oauth") ? "oauth" : "scheduler",
        platform: input.errorAutoLog.providerId,
      });
    }
  }

  if (input.errorPublishLog && !isStaleSetupError(input.errorPublishLog.message)) {
    candidates.push({
      timestamp: input.errorPublishLog.timestamp,
      message: input.errorPublishLog.message,
      source: "publish",
      platform: input.errorPublishLog.platform,
    });
  }

  if (input.oauthError?.level === "ERROR" && !isStaleSetupError(input.oauthError.message)) {
    candidates.push({
      timestamp: input.oauthError.timestamp,
      message: input.oauthError.message,
      source: "oauth",
      platform: input.oauthError.providerId,
    });
  }

  if (!candidates.length) return null;
  candidates.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
  return candidates[0] ?? null;
}

export async function buildAutoPostMonitorSnapshot(
  userId: string,
  projectId?: string,
  now = new Date(),
): Promise<AutoPostMonitorSnapshot> {
  const diagnostics = await evaluateAutoPostDiagnostics(userId, projectId, now);
  const project = diagnostics.projects[0] ?? null;

  const { getUserProjects } = await import("@/lib/storage/projects");
  const snapshot = await getUserProjects(userId);
  const activeProject = projectId
    ? snapshot?.projects.find((p) => p.id === projectId)
    : snapshot?.projects[0];
  const projectTimezone = activeProject?.timezone ?? "UTC";
  const data = activeProject ? snapshot?.dataByProjectId[activeProject.id] : undefined;
  const actualPostsPerDay = (data?.schedule?.postsPerDay ?? 2) as 1 | 2;
  const actualSeed = activeProject
    ? resolveScheduleSeed(activeProject.id, data?.schedule?.scheduleSeed)
    : 42;

  const slotInfo = computeNextScheduleSlot({
    timezone: projectTimezone,
    postsPerDay: actualPostsPerDay,
    scheduleSeed: actualSeed,
    now,
  });

  const autoLogs = await readAutoPostLogs({
    projectId: activeProject?.id,
    limit: 200,
  });
  const publishLogs = await readPublishLogs({
    projectId: activeProject?.id,
    limit: 100,
  });

  const attemptLog = autoLogs.find((l) => ATTEMPT_EVENTS.has(l.event));
  const errorAutoLog = autoLogs.find(
    (l) => l.level === "ERROR" && !isStaleSetupError(l.errorMessage ?? l.message),
  );
  const errorPublishLog = publishLogs.find(
    (l) => l.level === "error" && !isStaleSetupError(l.message),
  );
  const lastPublishedLog = publishLogs.find((l) => l.action === "published" && l.level === "success");

  const lockState = getAutoPostLockState();
  const schedulerHealth = await evaluateSchedulerHealth({ autoStartPoller: true, now });

  const allowManualRun =
    process.env.ALLOW_MANUAL_AUTO_POST === "true" || process.env.NODE_ENV === "development";

  const platforms: AutoPostMonitorPlatform[] = (project?.providers ?? []).map((p) => {
    const { label, tone } = platformStatusTone(
      p.connectionStatus !== "disconnected",
      p.ready,
      p.connectionStatus,
    );
    return {
      platform: p.platform,
      label: platformLabel(p.platform),
      connected: p.connectionStatus !== "disconnected",
      ready: p.ready,
      connectionStatus: p.connectionStatus,
      statusLabel: label,
      statusTone: tone,
      lastSuccessAt: p.lastSuccessAt,
      lastError: p.lastError && !isStaleSetupError(p.lastError) ? p.lastError : undefined,
    };
  });

  const oauthError = autoLogs.find(
    (l) => l.event === "oauth_connect_failed" || l.event.includes("oauth"),
  );

  const lastError = pickActiveLastError({
    errorAutoLog,
    errorPublishLog,
    oauthError,
  });

  const nextSlot = slotInfo.nextSlot
    ? {
        label: slotInfo.nextSlot.label,
        display: slotInfo.nextSlot.display,
        dayLabel: slotInfo.nextSlot.dayLabel,
        slotTimeLocal: slotInfo.nextSlot.slotTimeLocal,
        slotAt: slotInfo.nextSlot.slotAt,
        windowOpensAt: slotInfo.nextSlot.windowOpensAt,
        windowClosesAt: slotInfo.nextSlot.windowClosesAt,
        isWindowActive: slotInfo.nextSlot.isWindowActive,
        startsInLabel: slotInfo.nextSlot.isWindowActive
          ? "Active now"
          : formatDuration(slotInfo.nextSlot.msUntilWindowOpens),
        endsInLabel: formatDuration(slotInfo.nextSlot.msUntilWindowCloses),
      }
    : null;

  const scheduleStatusLabel =
    slotInfo.scheduleStatus === "active_window"
      ? "Inside schedule window"
      : "Waiting for next schedule";

  const dateStr = now.toISOString().slice(0, 10);

  return {
    checkedAt: now.toISOString(),
    projectId: activeProject?.id ?? null,
    projectName: activeProject?.name ?? project?.projectName ?? null,
    scheduler: {
      status: schedulerHealth.status,
      statusLabel: schedulerHealth.statusLabel,
      cronSecretConfigured: schedulerHealth.cronSecretConfigured,
      internalPollerEnabled: schedulerHealth.internalPollerEnabled,
      pollerStarted: schedulerHealth.pollerStartedOnWorker,
      tickInProgress: schedulerHealth.tickInProgress,
      intervalMinutes: schedulerHealth.intervalMinutes,
      lastRunAt: schedulerHealth.lastTickAt ?? lockState.lastRunAt,
      lastPollerStartAt: schedulerHealth.lastPollerStartAt,
      minutesSinceLastTick: schedulerHealth.minutesSinceLastTick,
    },
    timezone: {
      raw: projectTimezone,
      iana: parseProjectTimezone(projectTimezone),
      label: slotInfo.timezoneLabel,
      currentTimeLocal: slotInfo.currentTimeLocal,
      currentDateLocal: slotInfo.currentDateLocal,
    },
    schedule: {
      postsPerDay: actualPostsPerDay,
      plannedSlots: project?.plannedSlots ?? slotInfo.allSlotsToday ?? [],
      windowMinutes: SLOT_WINDOW_MINUTES,
      status: slotInfo.scheduleStatus,
      statusLabel: scheduleStatusLabel,
      nextSlot,
      activeSlotsNow: slotInfo.activeSlotsNow ?? [],
    },
    lastAttempt: attemptLog
      ? {
          timestamp: attemptLog.timestamp,
          event: attemptLog.event,
          message: attemptLog.message,
          resultLabel: attemptResultLabel(attemptLog.event, attemptLog.message),
          resultTone: attemptResultTone(attemptLog.event, attemptLog.level),
        }
      : null,
    lastPublished: lastPublishedLog
      ? {
          platform: platformLabel(lastPublishedLog.platform ?? "unknown"),
          timestamp: lastPublishedLog.timestamp,
          message: lastPublishedLog.message,
          permalink:
            typeof lastPublishedLog.details?.permalink === "string"
              ? lastPublishedLog.details.permalink
              : undefined,
        }
      : null,
    lastError,
    platforms: platforms ?? [],
    queuedCount: project?.queuedCount ?? 0,
    canAutoPost: project?.canAutoPost ?? false,
    executionStopsAt: project?.executionStopsAt ?? null,
    summary: diagnostics.summary,
    allowManualRun,
    logFiles: {
      autoPost: `logs/auto-post-${dateStr}.log`,
      publish: `logs/publish-${dateStr}.log`,
      appErrors: `logs/app-errors-${dateStr}.log`,
      generation: `logs/generation-${dateStr}.log`,
    },
  };
}

export function formatMonitorTimestamp(iso: string, timeZone?: string): string {
  const date = new Date(iso);
  if (Number.isNaN(date.getTime())) return iso;
  const tz = timeZone ? parseProjectTimezone(timeZone) : "UTC";
  try {
    if (timeZone) {
      return formatLocalDateTime(date, tz);
    }
  } catch {
    // fall through
  }
  try {
    return new Intl.DateTimeFormat("en-GB", {
      day: "2-digit",
      month: "short",
      year: "numeric",
      hour: "2-digit",
      minute: "2-digit",
      timeZone: "UTC",
      hour12: false,
    })
      .format(date)
      .replace(",", "");
  } catch {
    return date.toISOString();
  }
}
