import jwt from 'jsonwebtoken';
import { config } from '../config.js';
import { getSetting, getBooleanSetting } from '../models/settingsModel.js';

// Microsoft Entra ID (Azure AD) OAuth 2.0 authorization-code login.
//
// Tenant/client/callback values are read from the effective settings so admins
// can adjust them in the Config screen, while the client secret stays in the
// process environment (never persisted to the settings table). The flow uses
// Node's global fetch and the existing jsonwebtoken dependency for signed
// state, so no new packages are required.

const AUTHORITY_HOST = 'https://login.microsoftonline.com';
const GRAPH_ME_URL = 'https://graph.microsoft.com/v1.0/me';
const LOGIN_SCOPE = 'openid profile email https://graph.microsoft.com/User.Read';

export function entraSettings() {
  return {
    enabled: getBooleanSetting('entra_enabled', config.entra.enabled),
    tenantId: getSetting('entra_tenant_id') || config.entra.tenantId,
    clientId: getSetting('entra_client_id') || config.entra.clientId,
    clientSecret: config.entra.clientSecret,
    callbackUrl: getSetting('entra_callback_url') || config.entra.callbackUrl
  };
}

// Login is only offered when Entra is enabled AND fully configured. Missing any
// piece (common in dev) means we fall back to local auth without erroring.
export function isEntraLoginConfigured() {
  const s = entraSettings();
  return Boolean(s.enabled && s.tenantId && s.clientId && s.clientSecret && s.callbackUrl);
}

export function signState(payload = {}) {
  return jwt.sign({ ...payload, purpose: 'entra-state' }, config.jwtSecret, { expiresIn: '10m' });
}

export function verifyState(state) {
  const decoded = jwt.verify(state, config.jwtSecret);
  if (decoded.purpose !== 'entra-state') throw new Error('Invalid state');
  return decoded;
}

export function buildAuthorizeUrl(state) {
  const s = entraSettings();
  const params = new URLSearchParams({
    client_id: s.clientId,
    response_type: 'code',
    redirect_uri: s.callbackUrl,
    response_mode: 'query',
    scope: LOGIN_SCOPE,
    state
  });
  return `${AUTHORITY_HOST}/${encodeURIComponent(s.tenantId)}/oauth2/v2.0/authorize?${params.toString()}`;
}

export async function exchangeCodeForToken(code) {
  const s = entraSettings();
  const response = await fetch(`${AUTHORITY_HOST}/${encodeURIComponent(s.tenantId)}/oauth2/v2.0/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      client_id: s.clientId,
      client_secret: s.clientSecret,
      grant_type: 'authorization_code',
      code,
      redirect_uri: s.callbackUrl,
      scope: LOGIN_SCOPE
    })
  });
  const payload = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(payload.error_description || payload.error || `Token exchange failed: ${response.status}`);
  return payload;
}

export async function fetchEntraProfile(accessToken) {
  const response = await fetch(GRAPH_ME_URL, { headers: { Authorization: `Bearer ${accessToken}` } });
  const payload = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(payload?.error?.message || `Profile lookup failed: ${response.status}`);
  const email = (payload.mail || payload.userPrincipalName || '').toLowerCase();
  if (!email) throw new Error('Entra profile did not include an email or user principal name');
  return {
    email,
    displayName: payload.displayName || email,
    oid: payload.id || ''
  };
}
