import { randomUUID } from "crypto";
import { promises as fs } from "fs";
import path from "path";

const LOGS_DIR = path.join(process.cwd(), "logs");

export type AutoPostLogLevel = "INFO" | "WARNING" | "ERROR" | "SUCCESS";

export interface AutoPostLogEntry {
  timestamp: string;
  level: AutoPostLogLevel;
  event: string;
  jobId?: string;
  projectId?: string;
  opportunityId?: string;
  providerId?: string;
  status?: string;
  message: string;
  errorMessage?: string;
  stackTrace?: string;
  details?: Record<string, unknown>;
}

export interface AutoPostLogInput {
  level: AutoPostLogLevel;
  event: string;
  jobId?: string;
  projectId?: string;
  opportunityId?: string;
  providerId?: string;
  status?: string;
  message: string;
  error?: unknown;
  details?: Record<string, unknown>;
}

async function ensureLogsDir() {
  await fs.mkdir(LOGS_DIR, { recursive: true });
}

function logFilename(date = new Date()) {
  return `auto-post-${date.toISOString().slice(0, 10)}.log`;
}

function logFilePath(date = new Date()) {
  return path.join(LOGS_DIR, logFilename(date));
}

function formatError(error: unknown): { errorMessage?: string; stackTrace?: string } {
  if (!error) return {};
  if (error instanceof Error) {
    return { errorMessage: error.message, stackTrace: error.stack };
  }
  return { errorMessage: String(error) };
}

export function createAutoPostJobId(): string {
  return `job_${randomUUID().slice(0, 8)}`;
}

export async function logAutoPost(input: AutoPostLogInput): Promise<AutoPostLogEntry> {
  await ensureLogsDir();
  const { errorMessage, stackTrace } = formatError(input.error);
  const entry: AutoPostLogEntry = {
    timestamp: new Date().toISOString(),
    level: input.level,
    event: input.event,
    jobId: input.jobId,
    projectId: input.projectId,
    opportunityId: input.opportunityId,
    providerId: input.providerId,
    status: input.status,
    message: input.message,
    errorMessage,
    stackTrace,
    details: input.details,
  };
  await fs.appendFile(logFilePath(), `${JSON.stringify(entry)}\n`, "utf-8");
  return entry;
}

export async function readAutoPostLogs(input?: {
  projectId?: string;
  jobId?: string;
  limit?: number;
  date?: string;
}): Promise<AutoPostLogEntry[]> {
  const limit = input?.limit ?? 100;
  const file = input?.date
    ? path.join(LOGS_DIR, `auto-post-${input.date}.log`)
    : logFilePath();

  let raw = "";
  try {
    raw = await fs.readFile(file, "utf-8");
  } catch {
    return [];
  }

  const lines = raw.trim().split("\n").filter(Boolean);
  const entries: AutoPostLogEntry[] = [];

  for (const line of lines) {
    try {
      entries.push(JSON.parse(line) as AutoPostLogEntry);
    } catch {
      // skip malformed
    }
  }

  let filtered = entries;
  if (input?.projectId) {
    filtered = filtered.filter((e) => e.projectId === input.projectId);
  }
  if (input?.jobId) {
    filtered = filtered.filter((e) => e.jobId === input.jobId);
  }

  return filtered.slice(-limit).reverse();
}

/** Scoped logger for a single cron tick / slot attempt. */
export class AutoPostJobLogger {
  readonly jobId: string;

  constructor(jobId?: string) {
    this.jobId = jobId ?? createAutoPostJobId();
  }

  async info(
    event: string,
    message: string,
    ctx?: Omit<AutoPostLogInput, "level" | "event" | "message" | "jobId">,
  ) {
    return logAutoPost({ level: "INFO", event, message, jobId: this.jobId, ...ctx });
  }

  async success(
    event: string,
    message: string,
    ctx?: Omit<AutoPostLogInput, "level" | "event" | "message" | "jobId">,
  ) {
    return logAutoPost({
      level: "SUCCESS",
      event,
      message,
      jobId: this.jobId,
      status: ctx?.status ?? "success",
      ...ctx,
    });
  }

  async warn(
    event: string,
    message: string,
    ctx?: Omit<AutoPostLogInput, "level" | "event" | "message" | "jobId">,
  ) {
    return logAutoPost({ level: "WARNING", event, message, jobId: this.jobId, ...ctx });
  }

  async error(
    event: string,
    message: string,
    ctx?: Omit<AutoPostLogInput, "level" | "event" | "message" | "jobId" | "error"> & {
      error?: unknown;
    },
  ) {
    return logAutoPost({
      level: "ERROR",
      event,
      message,
      jobId: this.jobId,
      status: ctx?.status ?? "failed",
      ...ctx,
    });
  }
}
