'use strict';

const crypto = require('crypto');
const { ALL_SCOPES } = require('./auth-rules');

const TEST_REMOTE_HEADER = 'x-ctm-test-remote';

function parseScopes(value) {
  const out = new Set();
  const raw = Array.isArray(value) ? value : String(value || '').split(',');
  for (const s of raw) {
    const scope = String(s || '').trim();
    if (ALL_SCOPES.includes(scope)) out.add(scope);
  }
  if (out.has('admin')) {
    for (const s of ALL_SCOPES) out.add(s);
  }
  return out;
}

function allScopes() {
  return new Set(ALL_SCOPES);
}

function parseScopedTokenEnv(raw = process.env.CTM_AUTH_TEST_TOKENS || '') {
  if (!raw) return new Map();
  const map = new Map();
  try {
    const parsed = JSON.parse(raw);
    if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
      for (const [token, value] of Object.entries(parsed)) {
        if (!token) continue;
        const entry = Array.isArray(value) ? { scopes: value } : (value || {});
        map.set(token, {
          label: entry.label || 'env-scoped-token',
          scopes: parseScopes(entry.scopes || value),
        });
      }
      return map;
    }
  } catch {}

  for (const part of String(raw).split(';')) {
    const [token, scopes] = part.split(':');
    if (!token || !scopes) continue;
    map.set(token.trim(), { label: 'env-scoped-token', scopes: parseScopes(scopes) });
  }
  return map;
}

function shouldForceRemoteForTest(req) {
  return process.env.CTM_AUTH_TRUST_TEST_REMOTE_HEADER === '1'
    && String(req.headers?.[TEST_REMOTE_HEADER] || '') === '1';
}

function hashTokenForLog(token) {
  if (!token) return '';
  return crypto.createHash('sha256').update(String(token)).digest('hex').slice(0, 12);
}

function resolveAuthContext(req, url, options = {}) {
  const isLocalhost = options.isLocalhost || (() => false);
  const getAuthToken = options.getAuthToken || (() => null);
  const config = options.config || {};
  const forcedRemote = shouldForceRemoteForTest(req);
  const isLoopback = !forcedRemote && !!isLocalhost(req);

  if (isLoopback) {
    return {
      authenticated: true,
      isLoopback: true,
      deviceId: 'loopback',
      scopes: allScopes(),
      stepUpFresh: true,
      tokenLabel: 'loopback',
      transport: 'loopback',
      source: 'loopback',
    };
  }

  const token = getAuthToken(req, url);
  if (token && config.token && token === config.token) {
    return {
      authenticated: false,
      isLoopback: false,
      deviceId: null,
      scopes: new Set(),
      stepUpFresh: false,
      tokenLabel: null,
      transport: 'remote',
      source: 'legacy-remote-disabled',
      tokenHashPrefix: hashTokenForLog(token),
    };
  }

  const scoped = token ? parseScopedTokenEnv().get(token) : null;
  if (scoped) {
    return {
      authenticated: true,
      isLoopback: false,
      deviceId: `env-${hashTokenForLog(token)}`,
      scopes: scoped.scopes,
      stepUpFresh: false,
      tokenLabel: scoped.label,
      transport: 'remote',
      source: 'env-scoped-token',
      tokenHashPrefix: hashTokenForLog(token),
    };
  }

  if (token && typeof options.resolveDeviceToken === 'function') {
    const resolved = options.resolveDeviceToken(token, req, url);
    if (resolved && resolved.authenticated) {
      return {
        authenticated: true,
        isLoopback: false,
        deviceId: resolved.deviceId || null,
        scopes: resolved.scopes instanceof Set ? resolved.scopes : parseScopes(resolved.scopes),
        stepUpFresh: !!resolved.stepUpFresh,
        stepUpSessionHashPrefix: resolved.stepUpSessionHashPrefix || '',
        tokenLabel: resolved.tokenLabel || null,
        transport: resolved.transport || 'remote',
        source: resolved.source || 'device-token',
        tokenHashPrefix: resolved.tokenHashPrefix || hashTokenForLog(token),
        device: resolved.device || null,
      };
    }
    if (resolved && resolved.code) {
      return {
        authenticated: false,
        isLoopback: false,
        deviceId: resolved.deviceId || null,
        scopes: new Set(),
        stepUpFresh: false,
        tokenLabel: null,
        transport: 'remote',
        source: resolved.code,
        retryable: !!resolved.retryable,
        tokenHashPrefix: hashTokenForLog(token),
      };
    }
  }

  return {
    authenticated: false,
    isLoopback: false,
    deviceId: null,
    scopes: new Set(),
    stepUpFresh: false,
    tokenLabel: null,
    transport: 'remote',
    source: token ? 'invalid-token' : 'missing-token',
    tokenHashPrefix: hashTokenForLog(token),
  };
}

function publicAuthContext(auth) {
  return {
    authenticated: !!auth?.authenticated,
    isLoopback: !!auth?.isLoopback,
    deviceId: auth?.deviceId || null,
    scopes: Array.from(auth?.scopes || []).sort(),
    stepUpFresh: !!auth?.stepUpFresh,
    tokenLabel: auth?.tokenLabel || null,
    transport: auth?.transport || null,
    source: auth?.source || null,
    // Device-token absolute-cap hint: the phone shows "re-pair soon" while the
    // token still works instead of hitting a surprise lockout at the deadline.
    hardExpiresAt: auth?.hardExpiresAt || null,
    repairSoon: !!auth?.repairSoon,
  };
}

module.exports = {
  TEST_REMOTE_HEADER,
  parseScopedTokenEnv,
  parseScopes,
  publicAuthContext,
  resolveAuthContext,
  shouldForceRemoteForTest,
};
