import { Client as FtpClient } from "basic-ftp";
import SftpClient from "ssh2-sftp-client";
import { Writable } from "stream";
import { prisma } from "./db";
import { uploadBuffer } from "./storage";
import { enqueueProcessing } from "./processing-queue";

const ALLOWED_EXTENSIONS = [".pdf", ".jpg", ".jpeg", ".png", ".webp"];
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MB
const CONNECT_TIMEOUT = 15_000;

type Protocol = "sftp" | "ftp" | "ftps";

interface ConnectionConfig {
  host: string;
  port: number;
  user: string;
  pass: string;
  protocol: Protocol;
  incomingDir: string;
  processedDir?: string;
}

interface FileEntry {
  name: string;
  size: number;
  isDirectory: boolean;
}

function log(msg: string, ...args: unknown[]) {
  console.log(`[ftp-watcher] ${msg}`, ...args);
}

function logError(msg: string, ...args: unknown[]) {
  console.error(`[ftp-watcher] ${msg}`, ...args);
}

// ── SFTP helpers ──────────────────────────────────────────────────────────

async function sftpConnect(config: ConnectionConfig): Promise<SftpClient> {
  const sftp = new SftpClient();
  await sftp.connect({
    host: config.host,
    port: config.port,
    username: config.user,
    password: config.pass,
    readyTimeout: CONNECT_TIMEOUT,
    retries: 0,
  });
  return sftp;
}

async function sftpList(sftp: SftpClient, dir: string): Promise<FileEntry[]> {
  const items = await sftp.list(dir);
  return items.map((f) => ({
    name: f.name,
    size: f.size,
    isDirectory: f.type === "d",
  }));
}

async function sftpDownload(sftp: SftpClient, remotePath: string): Promise<Buffer> {
  const result = await sftp.get(remotePath);
  if (Buffer.isBuffer(result)) return result;
  if (typeof result === "string") return Buffer.from(result);
  return Buffer.from(result as unknown as ArrayBuffer);
}

async function sftpMove(
  sftp: SftpClient,
  src: string,
  dest: string,
  destDir: string
): Promise<void> {
  try {
    const exists = await sftp.exists(destDir);
    if (!exists) await sftp.mkdir(destDir, true);
    await sftp.rename(src, dest);
  } catch {
    log(`Could not move file to processed dir, removing instead`);
    try {
      await sftp.delete(src);
    } catch {}
  }
}

// ── FTP / FTPS helpers ────────────────────────────────────────────────────

async function ftpConnect(config: ConnectionConfig): Promise<FtpClient> {
  const client = new FtpClient(CONNECT_TIMEOUT);
  await client.access({
    host: config.host,
    port: config.port,
    user: config.user,
    password: config.pass,
    secure: config.protocol === "ftps",
    secureOptions: { rejectUnauthorized: false },
  });
  return client;
}

async function ftpDownloadToBuffer(client: FtpClient, remotePath: string): Promise<Buffer> {
  const chunks: Buffer[] = [];
  const writable = new Writable({
    write(chunk, _encoding, callback) {
      chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
      callback();
    },
  });
  await client.downloadTo(writable, remotePath);
  return Buffer.concat(chunks);
}

// ── Poll (main entry point) ──────────────────────────────────────────────

export async function pollFtp(): Promise<{ processed: number; skipped: number; error?: string }> {
  const settings = await prisma.appSettings.findUnique({ where: { id: "singleton" } });

  if (!settings?.ftpEnabled) {
    return { processed: 0, skipped: 0, error: "FTP watching is disabled" };
  }

  if (!settings.ftpHost || !settings.ftpUser || !settings.ftpPass) {
    return { processed: 0, skipped: 0, error: "FTP not configured" };
  }

  const protocol = (settings.ftpProtocol || "sftp") as Protocol;
  const config: ConnectionConfig = {
    host: settings.ftpHost,
    port: settings.ftpPort,
    user: settings.ftpUser,
    pass: settings.ftpPass,
    protocol,
    incomingDir: settings.ftpIncomingDir,
    processedDir: settings.ftpProcessedDir,
  };

  let processed = 0;
  let skipped = 0;

  if (protocol === "sftp") {
    const sftp = await sftpConnect(config).catch((err) => {
      throw new Error(`SFTP connect failed: ${err.message}`);
    });

    try {
      log(`Connected via SFTP to ${config.host}:${config.port}`);
      const allFiles = await sftpList(sftp, config.incomingDir);
      const validFiles = allFiles.filter((f) => {
        if (f.isDirectory) return false;
        const ext = f.name.toLowerCase().match(/\.[^.]+$/)?.[0] || "";
        return ALLOWED_EXTENSIONS.includes(ext);
      });

      log(`Found ${validFiles.length} file(s) in ${config.incomingDir}`);

      for (const file of validFiles) {
        const remotePath = `${config.incomingDir}/${file.name}`;
        const ext = file.name.toLowerCase().match(/\.[^.]+$/)?.[0] || "";

        if (file.size > MAX_FILE_SIZE) {
          log(`Skipping "${file.name}" — exceeds 50 MB`);
          skipped++;
          continue;
        }

        try {
          const buffer = await sftpDownload(sftp, remotePath);
          const isPdf = ext === ".pdf";
          const contentType = isPdf ? "application/pdf" : "image/jpeg";

          const job = await prisma.processingJob.create({
            data: { fileName: file.name, filePath: `ftp/${file.name}`, status: "queued" },
          });

          await uploadBuffer(`sources/${job.id}/${file.name}`, buffer, contentType);
          await enqueueProcessing(job.id);

          await sftpMove(
            sftp,
            remotePath,
            `${config.processedDir}/${file.name}`,
            config.processedDir!
          );

          processed++;
          log(`Queued: ${file.name} (job ${job.id})`);
        } catch (err) {
          logError(`Failed to process "${file.name}":`, err);
          skipped++;
        }
      }

      await sftp.end();
    } catch (err) {
      await sftp.end().catch(() => {});
      const message = err instanceof Error ? err.message : "SFTP poll failed";
      logError("Error:", message);
      return { processed, skipped, error: message };
    }
  } else {
    let client: FtpClient | null = null;
    try {
      client = await ftpConnect(config);
      log(`Connected via ${protocol.toUpperCase()} to ${config.host}:${config.port}`);

      const files = await client.list(config.incomingDir);
      const validFiles = files.filter((f) => {
        if (f.isDirectory) return false;
        const ext = f.name.toLowerCase().match(/\.[^.]+$/)?.[0] || "";
        return ALLOWED_EXTENSIONS.includes(ext);
      });

      log(`Found ${validFiles.length} file(s) in ${config.incomingDir}`);

      for (const file of validFiles) {
        const remotePath = `${config.incomingDir}/${file.name}`;
        const ext = file.name.toLowerCase().match(/\.[^.]+$/)?.[0] || "";

        if (file.size > MAX_FILE_SIZE) {
          log(`Skipping "${file.name}" — exceeds 50 MB`);
          skipped++;
          continue;
        }

        try {
          const buffer = await ftpDownloadToBuffer(client, remotePath);
          const isPdf = ext === ".pdf";
          const contentType = isPdf ? "application/pdf" : "image/jpeg";

          const job = await prisma.processingJob.create({
            data: { fileName: file.name, filePath: `ftp/${file.name}`, status: "queued" },
          });

          await uploadBuffer(`sources/${job.id}/${file.name}`, buffer, contentType);
          await enqueueProcessing(job.id);

          const destPath = `${config.processedDir}/${file.name}`;
          try {
            await client.ensureDir(config.processedDir!);
            await client.rename(remotePath, destPath);
          } catch {
            log(`Could not move "${file.name}" to processed dir, removing instead`);
            try { await client.remove(remotePath); } catch {}
          }

          processed++;
          log(`Queued: ${file.name} (job ${job.id})`);
        } catch (err) {
          logError(`Failed to process "${file.name}":`, err);
          skipped++;
        }
      }

      client.close();
    } catch (err) {
      client?.close();
      const message = err instanceof Error ? err.message : "FTP poll failed";
      logError("Error:", message);
      return { processed, skipped, error: message };
    }
  }

  log(`Complete: ${processed} processed, ${skipped} skipped`);
  return { processed, skipped };
}

// ── Test connection ──────────────────────────────────────────────────────

export async function testFtpConnection(config: {
  host: string;
  port: number;
  user: string;
  pass: string;
  protocol: Protocol;
  incomingDir: string;
}): Promise<{ ok: boolean; files?: number; error?: string }> {
  if (config.protocol === "sftp") {
    const sftp = new SftpClient();
    try {
      await sftp.connect({
        host: config.host,
        port: config.port,
        username: config.user,
        password: config.pass,
        readyTimeout: CONNECT_TIMEOUT,
        retries: 0,
      });
      const files = await sftp.list(config.incomingDir);
      await sftp.end();
      return { ok: true, files: files.length };
    } catch (err) {
      await sftp.end().catch(() => {});
      const message = err instanceof Error ? err.message : "SFTP connection failed";
      return { ok: false, error: message };
    }
  } else {
    const client = new FtpClient(CONNECT_TIMEOUT);
    try {
      await client.access({
        host: config.host,
        port: config.port,
        user: config.user,
        password: config.pass,
        secure: config.protocol === "ftps",
        secureOptions: { rejectUnauthorized: false },
      });
      const files = await client.list(config.incomingDir);
      client.close();
      return { ok: true, files: files.length };
    } catch (err) {
      client.close();
      const message = err instanceof Error ? err.message : "FTP connection failed";
      return { ok: false, error: message };
    }
  }
}
