import "server-only";

import { EventEmitter } from "events";
import type { ImapFlow } from "imapflow";
import type { RowDataPacket } from "mysql2/promise";
import { getDbPool } from "@/lib/db";
import {
  createRemoteMailboxImapClient,
  decryptMailboxPassword,
  type RemoteMailboxCredentials,
} from "@/lib/mailbox-remote";
import { refreshMailboxByEmail } from "@/lib/official-mail";

const WATCHED_MAILBOX_PATH = "INBOX";
const HEARTBEAT_IDLE_RESTART_MS = 29 * 60 * 1000;
const IMAP_SOCKET_TIMEOUT_MS = 35 * 60 * 1000;
const RECONNECT_DELAY_MS = 5_000;
const RETRY_DELAY_NO_AUTH_MS = 30_000;
const STOP_AFTER_IDLE_MS = 45_000;
const SYNC_DEBOUNCE_MS = 400;

type MailboxRealtimeReason = "exists" | "expunge" | "flags";

export type MailboxRealtimeEvent =
  | {
      type: "ready";
      at: string;
      mailbox: string;
    }
  | {
      type: "mailbox-updated";
      at: string;
      delta: number;
      mailbox: string;
      reason: MailboxRealtimeReason;
    }
  | {
      type: "error";
      at: string;
      mailbox: string;
      message: string;
    };

type MailboxRealtimeListener = (event: MailboxRealtimeEvent) => void;

type MailboxAuthRow = RowDataPacket & {
  email: string;
  password_ciphertext: string | null;
};

type PendingSync = {
  delta: number;
  reason: MailboxRealtimeReason;
};

async function loadMailboxCredentialsByEmail(email: string): Promise<RemoteMailboxCredentials | null> {
  const [rows] = await getDbPool().query<MailboxAuthRow[]>(
    `
      SELECT m.email, m.password_ciphertext
      FROM mailboxes m
      WHERE m.email = ?
      ORDER BY m.id DESC
      LIMIT 1
    `,
    [email],
  );

  const mailbox = rows[0];

  if (!mailbox?.password_ciphertext) {
    return null;
  }

  return {
    email: mailbox.email,
    password: decryptMailboxPassword(mailbox.password_ciphertext),
  };
}

function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

class MailboxRealtimeWatcher {
  private active = false;
  private client: ImapFlow | null = null;
  private emitter = new EventEmitter();
  private pendingSync: PendingSync | null = null;
  private reconnectPromise: Promise<void> | null = null;
  private stopTimer: NodeJS.Timeout | null = null;
  private syncInFlight = false;
  private syncTimer: NodeJS.Timeout | null = null;

  constructor(
    private readonly email: string,
    private readonly onDisposable: () => void,
  ) {
    this.emitter.setMaxListeners(0);
  }

  subscribe(listener: MailboxRealtimeListener) {
    this.emitter.on("event", listener);
    this.cancelStopTimer();

    if (!this.active) {
      this.active = true;
      this.reconnectPromise = this.run();
    }

    return () => {
      this.emitter.off("event", listener);

      if (this.emitter.listenerCount("event") === 0) {
        this.scheduleStop();
      }
    };
  }

  private emit(event: MailboxRealtimeEvent) {
    this.emitter.emit("event", event);
  }

  private scheduleStop() {
    this.cancelStopTimer();
    this.stopTimer = setTimeout(() => {
      void this.stop();
    }, STOP_AFTER_IDLE_MS);
  }

  private cancelStopTimer() {
    if (!this.stopTimer) {
      return;
    }

    clearTimeout(this.stopTimer);
    this.stopTimer = null;
  }

  private queueSync(reason: MailboxRealtimeReason, delta = 0) {
    if (!this.pendingSync) {
      this.pendingSync = { reason, delta: Math.max(0, delta) };
    } else {
      this.pendingSync = {
        reason:
          this.pendingSync.reason === "exists" || reason === "exists"
            ? "exists"
            : reason,
        delta: this.pendingSync.delta + Math.max(0, delta),
      };
    }

    if (this.syncTimer) {
      return;
    }

    this.syncTimer = setTimeout(() => {
      this.syncTimer = null;
      void this.flushSync();
    }, SYNC_DEBOUNCE_MS);
  }

  private async flushSync() {
    if (this.syncInFlight || !this.pendingSync) {
      return;
    }

    this.syncInFlight = true;
    const nextSync = this.pendingSync;
    this.pendingSync = null;

    try {
      await refreshMailboxByEmail(this.email, { folder: "inbox" });
      this.emit({
        type: "mailbox-updated",
        at: new Date().toISOString(),
        delta: nextSync.delta,
        mailbox: this.email,
        reason: nextSync.reason,
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : "Mailbox realtime sync failed";
      console.warn(
        `[mailbox-live] sync failed mailbox=${this.email} reason=${nextSync.reason} message=${message}`,
      );
      this.emit({
        type: "error",
        at: new Date().toISOString(),
        mailbox: this.email,
        message,
      });
    } finally {
      this.syncInFlight = false;

      if (this.pendingSync && !this.syncTimer) {
        this.syncTimer = setTimeout(() => {
          this.syncTimer = null;
          void this.flushSync();
        }, SYNC_DEBOUNCE_MS);
      }
    }
  }

  private async closeClient(client: ImapFlow | null) {
    if (!client) {
      return;
    }

    try {
      await client.logout();
    } catch {
      client.close();
    }
  }

  private async run() {
    while (this.active) {
      if (this.emitter.listenerCount("event") === 0) {
        break;
      }

      let reconnectDelay = RECONNECT_DELAY_MS;
      let client: ImapFlow | null = null;

      try {
        const credentials = await loadMailboxCredentialsByEmail(this.email);

        if (!credentials) {
          reconnectDelay = RETRY_DELAY_NO_AUTH_MS;
          this.emit({
            type: "error",
            at: new Date().toISOString(),
            mailbox: this.email,
            message: "mailbox-auth-missing",
          });
        } else {
          client = createRemoteMailboxImapClient(credentials, {
            disableAutoIdle: true,
            logger: false,
            maxIdleTime: HEARTBEAT_IDLE_RESTART_MS,
            socketTimeout: IMAP_SOCKET_TIMEOUT_MS,
          });
          this.client = client;

          client.on("exists", (event) => {
            this.queueSync("exists", event.count - event.prevCount);
          });
          client.on("expunge", () => {
            this.queueSync("expunge");
          });
          client.on("flags", () => {
            this.queueSync("flags");
          });

          await client.connect();
          await client.mailboxOpen(WATCHED_MAILBOX_PATH, { readOnly: true });

          this.emit({
            type: "ready",
            at: new Date().toISOString(),
            mailbox: this.email,
          });

          while (
            this.active &&
            this.client === client &&
            this.emitter.listenerCount("event") > 0
          ) {
            await client.idle();
          }
        }
      } catch (error) {
        if (!this.active) {
          break;
        }

        const message = error instanceof Error ? error.message : "Mailbox realtime connection failed";
        console.warn(`[mailbox-live] watcher failed mailbox=${this.email} message=${message}`);

        this.emit({
          type: "error",
          at: new Date().toISOString(),
          mailbox: this.email,
          message,
        });
      } finally {
        this.client = null;
        await this.closeClient(client);
      }

      if (!this.active || this.emitter.listenerCount("event") === 0) {
        break;
      }

      await delay(reconnectDelay);
    }

    this.active = false;
    this.onDisposable();
  }

  async stop() {
    this.active = false;
    this.pendingSync = null;
    this.cancelStopTimer();

    if (this.syncTimer) {
      clearTimeout(this.syncTimer);
      this.syncTimer = null;
    }

    const client = this.client;
    this.client = null;
    await this.closeClient(client);
  }
}

class MailboxRealtimeManager {
  private watchers = new Map<string, MailboxRealtimeWatcher>();

  subscribe(email: string, listener: MailboxRealtimeListener) {
    const watcher = this.getWatcher(email);
    return watcher.subscribe(listener);
  }

  private getWatcher(email: string) {
    let watcher = this.watchers.get(email);

    if (watcher) {
      return watcher;
    }

    watcher = new MailboxRealtimeWatcher(email, () => {
      const current = this.watchers.get(email);

      if (current === watcher) {
        this.watchers.delete(email);
      }
    });
    this.watchers.set(email, watcher);
    return watcher;
  }
}

type MailboxRealtimeGlobal = typeof globalThis & {
  __officialMailRealtimeManager?: MailboxRealtimeManager;
};

function getMailboxRealtimeManager() {
  const mailboxRealtimeGlobal = globalThis as MailboxRealtimeGlobal;

  if (!mailboxRealtimeGlobal.__officialMailRealtimeManager) {
    mailboxRealtimeGlobal.__officialMailRealtimeManager = new MailboxRealtimeManager();
  }

  return mailboxRealtimeGlobal.__officialMailRealtimeManager;
}

export function subscribeMailboxLiveByEmail(
  email: string,
  listener: MailboxRealtimeListener,
) {
  return getMailboxRealtimeManager().subscribe(email, listener);
}
