import "server-only"

import { addMonths, addWeeks, isBefore } from "date-fns"
import type { ReportExportRun, ReportExportSchedule } from "@/generated/prisma/client"
import { REPORT_EXPORT_DEFINITIONS, type ReportExportKey } from "@/lib/report-definitions"

type SerializableSchedule = ReportExportSchedule & {
  reportKeys: ReportExportKey[] | null
  recipients: string[] | null
}

export function sanitizeReportKeys(keys: string[]): ReportExportKey[] {
  return Array.from(
    new Set(
      keys
        .map((key) => key as ReportExportKey)
        .filter((key): key is ReportExportKey => Boolean(REPORT_EXPORT_DEFINITIONS[key]))
    )
  )
}

export function sanitizeRecipients(recipients: string[]): string[] {
  return Array.from(
    new Set(
      recipients
        .map((value) => value.trim().toLowerCase())
        .filter((value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
    )
  )
}

type NextRunInput = {
  frequency: "daily" | "weekly" | "monthly"
  dayOfWeek?: number | null
  dayOfMonth?: number | null
  hour: number
  minute: number
  timezone: string
}

export function computeNextRunAt({ frequency, dayOfWeek, dayOfMonth, hour, minute, timezone }: NextRunInput, currentDate = new Date()): Date {
  const base = zonedDate(currentDate, timezone)
  let candidate = new Date(Date.UTC(base.getUTCFullYear(), base.getUTCMonth(), base.getUTCDate(), hour, minute, 0, 0))

  if (frequency === "daily") {
    if (!isBefore(base, candidate)) {
      candidate.setUTCDate(candidate.getUTCDate() + 1)
    }
    return candidate
  }

  if (frequency === "weekly") {
    const targetDow = typeof dayOfWeek === "number" ? clamp(dayOfWeek, 0, 6) : 1
    while (candidate.getUTCDay() !== targetDow || !isBefore(base, candidate)) {
      candidate = addWeeks(candidate, 1)
    }
    return candidate
  }

  // monthly
  const targetDay = typeof dayOfMonth === "number" ? clamp(dayOfMonth, 1, 28) : 1
  candidate.setUTCDate(targetDay)
  if (!isBefore(base, candidate)) {
    candidate = addMonths(candidate, 1)
    candidate.setUTCDate(Math.min(targetDay, daysInMonth(candidate)))
  }
  return candidate
}

function clamp(value: number, min: number, max: number) {
  return Math.max(min, Math.min(max, value))
}

function daysInMonth(date: Date) {
  const year = date.getUTCFullYear()
  const month = date.getUTCMonth()
  return new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
}

function zonedDate(date: Date, timeZone: string) {
  const formatter = new Intl.DateTimeFormat("en-US", {
    timeZone,
    hour12: false,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  })
  const parts = formatter.formatToParts(date)
  const get = (type: Intl.DateTimeFormatPartTypes) =>
    Number(parts.find((part) => part.type === type)?.value ?? "0")
  const year = get("year")
  const month = get("month")
  const day = get("day")
  const hour = get("hour")
  const minute = get("minute")
  const second = get("second")
  return new Date(Date.UTC(year, month - 1, day, hour, minute, second))
}

export function serializeSchedule(
  schedule: SerializableSchedule,
  runs: ReportExportRun[] = []
) {
  return {
    id: schedule.id,
    name: schedule.name,
    tenantId: schedule.tenantId,
    reportKeys: schedule.reportKeys ?? [],
    range: schedule.range,
    companyId: schedule.companyId,
    companyName: schedule.companyName,
    format: schedule.format,
    frequency: schedule.frequency,
    dayOfWeek: schedule.dayOfWeek,
    dayOfMonth: schedule.dayOfMonth,
    hour: schedule.hour,
    minute: schedule.minute,
    timezone: schedule.timezone,
    recipients: schedule.recipients ?? [],
    status: schedule.status,
    lastRunAt: schedule.lastRunAt?.toISOString() ?? null,
    nextRunAt: schedule.nextRunAt?.toISOString() ?? null,
    createdAt: schedule.createdAt.toISOString(),
    updatedAt: schedule.updatedAt.toISOString(),
    runs: runs.map((run) => ({
      id: run.id,
      status: run.status,
      startedAt: run.startedAt.toISOString(),
      completedAt: run.completedAt?.toISOString() ?? null,
      error: run.error,
      artifacts: Array.isArray(run.artifacts)
        ? (run.artifacts as Array<Record<string, unknown>>).map((artifact, index) => {
            const rawKey = artifact["key"]
            const rawName = artifact["fileName"]
            const rawMime = artifact["mimeType"]
            return {
              index,
              key: typeof rawKey === "string" ? rawKey : `arquivo-${index + 1}`,
              fileName: typeof rawName === "string" ? rawName : null,
              mimeType: typeof rawMime === "string" ? rawMime : null,
            }
          })
        : [],
    })),
  }
}
