import { refreshAndSaveTokens } from "../db/mail";
import { decrypt } from "./crypto";

/**
 * MailConnection-Shape: nur die Felder die für Auth-Resolution nötig sind.
 * Beide Scan-Endpoints bekommen das volle Prisma-Objekt — dieses Interface
 * dient als explizites Subset damit der Helper nicht vom vollen Typ abhängt.
 */
export interface MailConnectionAuthFields {
  id: string;
  email: string;
  authMethod: string;
  passwordEncrypted: string;
  oauthAccessToken?: string | null;
  oauthTokenExpiry?: Date | null;
}

export type ImapAuth =
  | { user: string; accessToken: string }
  | { user: string; pass: string };

export interface OauthClientIds {
  /** Azure App Registration Client ID (MS_OAUTH_CLIENT_ID). */
  msClientId: string;
  /** Google Cloud OAuth Client ID (GOOGLE_OAUTH_CLIENT_ID). */
  googleClientId: string;
}

/**
 * Gibt das korrekte `auth`-Objekt für ImapFlow zurück.
 *
 * - oauth2_microsoft: Access-Token decrypten, bei Ablauf via MS-Endpoint refreshen.
 *   Nutzt refreshAndSaveTokens() aus db/mail (Race-Condition-sicher, Prisma-basiert).
 * - oauth2_google: Analog zu oauth2_microsoft. Google rotiert refresh_tokens nicht,
 *   aber der 5-Minuten-Puffer-Refresh gilt trotzdem (access_token ist 1h gültig).
 * - Alle anderen authMethods (app_password, default): passwordEncrypted decrypten.
 *
 * Wirft wenn:
 *   - App-Password leer oder decrypt fehlschlägt
 *   - OAuth-Token fehlt und kein Refresh möglich
 *   - refreshAndSaveTokens() wirft (revoked refresh_token, Provider-Endpoint-Fehler)
 *
 * @param connection  MailConnection-Felder (Subset)
 * @param clientIds   Beide OAuth Client-IDs; die richtige wird anhand authMethod gewählt.
 */
export async function resolveImapAuth(
  connection: MailConnectionAuthFields,
  clientIds: OauthClientIds,
): Promise<ImapAuth> {
  if (
    connection.authMethod === "oauth2_microsoft" ||
    connection.authMethod === "oauth2_google"
  ) {
    // Wähle den richtigen Client-ID anhand des authMethod-Diskriminators.
    const clientId =
      connection.authMethod === "oauth2_google"
        ? clientIds.googleClientId
        : clientIds.msClientId;

    // Token-Expiry-Check: 5-Minuten-Puffer damit der Scan nicht
    // mitten in einem großen Mailbox-Durchlauf mit abgelaufenem Token stirbt.
    const fiveMinFromNow = Date.now() + 5 * 60 * 1000;
    const isExpiredOrMissing =
      !connection.oauthTokenExpiry ||
      connection.oauthTokenExpiry.getTime() < fiveMinFromNow;

    let accessToken: string;
    if (isExpiredOrMissing) {
      // Wirft wenn Refresh-Token fehlt oder Provider-Endpoint antwortet mit Fehler.
      // Caller (scan.post / scan-internal.post) soll per try/catch continue-n.
      accessToken = await refreshAndSaveTokens(connection.id, clientId);
    } else {
      if (!connection.oauthAccessToken) {
        throw new Error(
          `${connection.authMethod} connection ${connection.id} has no oauthAccessToken stored`,
        );
      }
      accessToken = decrypt(connection.oauthAccessToken);
    }

    return { user: connection.email, accessToken };
  }

  // App-Password-Pfad (gmail app-password, icloud, gmx, yahoo, custom)
  if (!connection.passwordEncrypted) {
    throw new Error(
      `Connection ${connection.id} has no passwordEncrypted (authMethod=${connection.authMethod})`,
    );
  }
  const pass = decrypt(connection.passwordEncrypted);
  return { user: connection.email, pass };
}
