import jwt, { type JwtPayload } from "jsonwebtoken";
import { Types } from "mongoose";

function smartcarOauthJwtSecret(): string {
  return String(process.env.JWT_SECRET ?? "").trim();
}

/** JWT `id` / Mongo ObjectId string — 24 hex chars. */
const MongoObjectIdHex = /^[a-fA-F0-9]{24}$/;

/** Restricted claim so we never treat a normal login JWT as OAuth `state`. */
const SMARTCAR_OAUTH_STATE_PURPOSE = "tank_track_smartcar_oauth_v1";

function resolveSmartcarOauthSessionMaxMs(): number {
  const raw = process.env.SMARTCAR_OAUTH_SESSION_MAX_MINUTES?.trim();
  if (!raw) return 120 * 60 * 1000;
  const n = Number(raw);
  if (!Number.isFinite(n)) return 120 * 60 * 1000;
  const clamped = Math.min(720, Math.max(5, n));
  return clamped * 60 * 1000;
}

const SESSION_MAX_MS = resolveSmartcarOauthSessionMaxMs();

export function parseTankUserIdFromOAuthState(
  stateParam: string | undefined
): string | null {
  let state = typeof stateParam === "string" ? stateParam.trim() : "";
  if (!state) return null;
  try {
    state = decodeURIComponent(state).trim();
  } catch {
    /* keep trimmed raw */
  }
  if (!MongoObjectIdHex.test(state) || !Types.ObjectId.isValid(state))
    return null;
  try {
    const oid = new Types.ObjectId(state);
    if (oid.toString().toLowerCase() !== state.toLowerCase()) return null;
    return state;
  } catch {
    return null;
  }
}

function clampSmartcarOauthStateTtlMinutes(): number {
  const raw = process.env.SMARTCAR_OAUTH_SESSION_MAX_MINUTES?.trim();
  const n = raw ? Number(raw) : NaN;
  const m = Number.isFinite(n) ? n : 120;
  return Math.min(720, Math.max(5, m));
}

/**
 * Value for Smartcar `state=` on the authorize URL. Survives system-browser OAuth (no session cookie)
 * when Smartcar echoes `state` back — see https://smartcar.com/docs/connect/handle-the-response
 */
export function signSmartcarOauthStateTankUserId(userId: string): string | null {
  const secret = smartcarOauthJwtSecret();
  if (!secret) return null;
  if (!parseTankUserIdFromOAuthState(userId)) return null;
  return jwt.sign(
    { sc_oauth: SMARTCAR_OAUTH_STATE_PURPOSE, uid: userId },
    secret,
    { expiresIn: `${clampSmartcarOauthStateTtlMinutes()}m`, algorithm: "HS256" }
  );
}

/**
 * Resolve Tank user id from Smartcar redirect `state`: legacy 24-hex `state` **or** signed JWT from
 * {@link signSmartcarOauthStateTankUserId}.
 */
export function resolveTankTrackUserIdFromSmartcarRedirectState(
  stateParam: string | undefined
): string | null {
  const legacy = parseTankUserIdFromOAuthState(stateParam);
  if (legacy) return legacy;

  const secret = smartcarOauthJwtSecret();
  if (!secret) return null;

  let raw = typeof stateParam === "string" ? stateParam.trim() : "";
  if (!raw) return null;
  try {
    raw = decodeURIComponent(raw).trim();
  } catch {
    /* keep raw */
  }

  try {
    const d = jwt.verify(raw, secret, {
      algorithms: ["HS256"],
    }) as JwtPayload;
    if (d.sc_oauth !== SMARTCAR_OAUTH_STATE_PURPOSE || typeof d.uid !== "string")
      return null;
    return parseTankUserIdFromOAuthState(d.uid.trim());
  } catch {
    return null;
  }
}

export type SmartcarOAuthSessionFields =
  | {
      smartcar_oauth_user_id?: string;
      smartcar_oauth_started_at?: number;
    }
  | undefined;

/**
 * Fallback when `state` is missing on the Smartcar redirect (WebView stripped it,
 * or Connect was started from Dashboard "Share Connect Link"). Works only if the
 * same browser kept the session cookie from GET /smartcar/login.
 */
export function parseTankUserIdFromSmartcarOAuthSession(
  session: SmartcarOAuthSessionFields
): string | null {
  if (!session || typeof session !== "object") return null;
  const raw = session.smartcar_oauth_user_id;
  const at = session.smartcar_oauth_started_at;
  if (typeof raw !== "string" || typeof at !== "number") return null;
  const out = parseTankUserIdFromOAuthState(raw);
  if (!out) return null;
  if (Date.now() - at > SESSION_MAX_MS) return null;
  return out;
}

export function clearSmartcarOAuthSessionFields(session: {
  smartcar_oauth_user_id?: unknown;
  smartcar_oauth_started_at?: unknown;
}): void {
  delete session.smartcar_oauth_user_id;
  delete session.smartcar_oauth_started_at;
}

/**
 * **Simulated / test mode only** (`SMARTCAR_MODE` ≠ `live`): when `state` and session are absent,
 * optionally link using a fixed **Tank Track** Mongo id from env (24 hex). Does **not** use Smartcar's
 * `user_id` UUID — that identifies the Smartcar connection, not your app user.
 *
 * For production / live mode, keep this unset and rely on signed `state` from GET /smartcar/login.
 */
export function trySmartcarSimulatorTankTrackUserFromEnv(): string | null {
  const mode = process.env.SMARTCAR_MODE?.trim().toLowerCase();
  const bridge =
    process.env.SMARTCAR_SIMULATOR_BRIDGE?.trim().toLowerCase() === "true";
  if (mode === "live" && !bridge) return null;

  const raw = process.env.SMARTCAR_SIMULATOR_TANK_TRACK_USER_ID?.trim();
  if (!raw) return null;

  return parseTankUserIdFromOAuthState(raw);
}
