import { createRouter } from '../../create-router';
import { BAD_REQUEST, NOT_FOUND } from '../../custom-errors';
import {
  getEmailAccounts,
  getEmailAccount,
  createEmailAccount,
  deleteEmailAccount,
  updateEmailAccountStatus,
} from 'officerdb';
import { getValidGoogleAccessToken } from '../../api/integrations/google-auth';
import { validateImapConnection } from './imap-validate';
import { startSync, isSyncing, getSyncStates } from './sync-runner';
import { imapSyncSteps } from './imap-sync';
import { gmailSyncSteps } from './gmail-api';
import { openEmailDb } from './store';
import { hasSyncedBefore } from './sync-meta';
import { performResync } from './resync';

type CreateAccountBody = {
  provider: string;
  email: string;
  displayName?: string;
  imapHost: string;
  imapPort: number;
  imapSecure: boolean;
  authType: string;
  credentials: Record<string, unknown>;
};

type ValidateBody = {
  imapHost: string;
  imapPort: number;
  imapSecure: boolean;
  authType: string;
  email: string;
  credentials: Record<string, unknown>;
};

export const accountsRouter = createRouter();

accountsRouter.get('/', async (ctx) => {
  const user = ctx.get('user');
  const accounts = await getEmailAccounts(user.id);

  // Check for stale syncing/queued accounts with no active job
  const staleIds: number[] = [];
  const hasActiveAccounts = accounts.some((a) => a.status === 'syncing' || a.status === 'queued');
  let activeJobAccountIds = new Set<number>();

  if (hasActiveAccounts) {
    // In-process now: a running sync is one this process started, not a queued job row. An account marked
    // syncing with nothing running is stale — the usual cause is a restart mid-sync.
    activeJobAccountIds = new Set(
      getSyncStates()
        .filter((s) => s.status === 'running')
        .map((s) => s.accountId),
    );

    for (const a of accounts) {
      if ((a.status === 'syncing' || a.status === 'queued') && !activeJobAccountIds.has(a.id)) {
        staleIds.push(a.id);
      }
    }

    // Reset stale accounts in background
    if (staleIds.length > 0) {
      for (const id of staleIds) {
        updateEmailAccountStatus(id, 'connected').catch(() => {});
      }
    }
  }

  return ctx.json(
    accounts.map((a) => ({
      id: a.id,
      provider: a.provider,
      email: a.email,
      displayName: a.displayName,
      enabled: a.enabled,
      status: staleIds.includes(a.id) ? 'connected' : a.status,
      createdAt: a.createdAt,
    })),
  );
});

accountsRouter.post('/', async (ctx) => {
  const user = ctx.get('user');
  const body = ctx.get('body') as CreateAccountBody;

  if (!body.provider || !body.email || !body.imapHost || !body.imapPort || !body.authType) {
    throw BAD_REQUEST('Missing required fields');
  }

  const authResult = await resolveAuth(user.id, body.authType, body.email, body.credentials);
  if (!authResult.ok) throw BAD_REQUEST(authResult.error);

  const validation = await validateImapConnection({
    host: body.imapHost,
    port: body.imapPort,
    secure: body.imapSecure,
    user: body.email,
    ...authResult.auth,
  });

  if (!validation.ok) throw BAD_REQUEST(`IMAP connection failed: ${validation.error}`);

  const account = await createEmailAccount({
    userId: user.id,
    provider: body.provider,
    email: body.email,
    displayName: body.displayName,
    imapHost: body.imapHost,
    imapPort: body.imapPort,
    imapSecure: body.imapSecure,
    authType: body.authType,
    credentials: body.credentials,
  });

  return ctx.json({ id: account.id, provider: account.provider, email: account.email }, 201);
});

accountsRouter.delete('/:id', async (ctx) => {
  const user = ctx.get('user');
  const id = Number(ctx.req.param('id'));
  const deleted = await deleteEmailAccount(id, user.id);
  if (!deleted) throw NOT_FOUND('Account not found');
  return ctx.json({ ok: true });
});

accountsRouter.post('/:id/sync', async (ctx) => {
  const user = ctx.get('user');
  const id = Number(ctx.req.param('id'));

  const account = await getEmailAccount(id);
  if (!account || account.userId !== user.id) throw NOT_FOUND('Account not found');

  if (account.status === 'queued') throw BAD_REQUEST('Sync is already queued');
  if (account.status === 'syncing') throw BAD_REQUEST('Account is already syncing');

  // Determine if this is a first sync or resync
  // One answer for both providers now. This used to fork: Gmail read SQLite, IMAP read the Postgres
  // column, which is how the two drifted apart in the first place.
  let isFirstSync = true;
  {
    const db = openEmailDb(user.email, account.email);
    try {
      isFirstSync = !hasSyncedBefore(db, account.syncMeta as Record<string, unknown>);
    } finally {
      db.close();
    }
  }

  // Resync: call directly, no job queue
  if (!isFirstSync) {
    await updateEmailAccountStatus(id, 'syncing');
    try {
      const result = await performResync({ accountId: id, userEmail: user.email, userId: user.id });
      await updateEmailAccountStatus(id, 'synced');
      return ctx.json({ ok: true, saved: result.saved });
    } catch (err) {
      await updateEmailAccountStatus(id, 'synced').catch(() => {});
      throw err;
    }
  }

  // First sync: enqueue a job
  const authResult = await resolveAuth(
    user.id,
    account.authType,
    account.email,
    account.credentials as Record<string, unknown>,
  );
  if (!authResult.ok) throw BAD_REQUEST(authResult.error);

  await updateEmailAccountStatus(id, 'queued');

  // Gmail API sync needs OAuth; a gmail account with an app password syncs over IMAP instead.
  const useGmailApi = account.provider === 'gmail' && account.authType === 'oauth';

  if (isSyncing(id)) return ctx.json({ ok: true, alreadyRunning: true });

  startSync({
    accountId: id,
    // The OWNER's email, not the account's — the store path is keyed by it.
    ownerEmail: user.email,
    steps: useGmailApi ? gmailSyncSteps : imapSyncSteps,
    onDone: (ok) => updateEmailAccountStatus(id, ok ? 'synced' : 'error').then(() => undefined),
    meta: {
      emailAccountId: id,
      userEmail: user.email,
      account: {
        id: account.id,
        userId: account.userId,
        email: account.email,
        imapHost: account.imapHost,
        imapPort: account.imapPort,
        imapSecure: account.imapSecure,
        provider: account.provider,
        authType: account.authType,
        credentials: account.credentials,
      },
      imapAuth: { user: account.email, ...authResult.auth },
    },
  });

  return ctx.json({ ok: true }, 201);
});

accountsRouter.post('/validate', async (ctx) => {
  const user = ctx.get('user');
  const body = ctx.get('body') as ValidateBody;

  if (!body.imapHost || !body.imapPort || !body.authType || !body.email) {
    throw BAD_REQUEST('Missing required fields');
  }

  const authResult = await resolveAuth(user.id, body.authType, body.email, body.credentials);
  if (!authResult.ok) return ctx.json({ ok: false, error: authResult.error });

  const result = await validateImapConnection({
    host: body.imapHost,
    port: body.imapPort,
    secure: body.imapSecure,
    user: body.email,
    ...authResult.auth,
  });

  return ctx.json(result);
});

type AuthResult = { ok: true; auth: { pass?: string; accessToken?: string } } | { ok: false; error: string };

async function resolveAuth(
  userId: number,
  authType: string,
  email: string,
  credentials: Record<string, unknown>,
): Promise<AuthResult> {
  if (authType === 'oauth') {
    const integrationId = credentials.userIntegrationId as number | undefined;
    if (!integrationId) return { ok: false, error: 'Missing userIntegrationId for OAuth' };

    let accessToken: string | null;
    try {
      accessToken = await getValidGoogleAccessToken(userId);
    } catch (err) {
      return {
        ok: false,
        error: `Token refresh failed — reconnect Google account: ${err instanceof Error ? err.message : err}`,
      };
    }
    if (!accessToken) return { ok: false, error: 'No access token available — reconnect Google account' };

    return { ok: true, auth: { accessToken } };
  }

  if (authType === 'password') {
    const pass = credentials.password as string | undefined;
    if (!pass) return { ok: false, error: 'Missing password' };
    return { ok: true, auth: { pass } };
  }

  return { ok: false, error: `Unknown auth type: ${authType}` };
}
