import { createHmac, timingSafeEqual } from 'crypto';

type RawPatientSessionPayload = {
  v: 1;
  acc: string;
  pat: string;
  email: string;
  exp: number;
};

export type VerifiedPatientSession = {
  accountId: string;
  patientId: string;
  email: string;
  expiresAt: string;
};

const defaultPatientSessionLifetimeMs = 1000 * 60 * 60 * 24 * 7;

const getPatientSessionSecret = () =>
  process.env.PATIENT_SESSION_SECRET?.trim() || 'dentaflow-dev-patient-session-secret';

const encodeSegment = (value: string) => Buffer.from(value, 'utf-8').toString('base64url');

const decodeSegment = (value: string) => Buffer.from(value, 'base64url').toString('utf-8');

const signSegment = (segment: string) =>
  createHmac('sha256', getPatientSessionSecret()).update(segment).digest('base64url');

export const createPatientSessionToken = (input: {
  accountId: string;
  patientId: string;
  email: string;
  expiresInMs?: number;
}) => {
  const expiresAtTimestamp = Date.now() + (input.expiresInMs ?? defaultPatientSessionLifetimeMs);
  const payload: RawPatientSessionPayload = {
    v: 1,
    acc: input.accountId,
    pat: input.patientId,
    email: input.email,
    exp: expiresAtTimestamp,
  };

  const encodedPayload = encodeSegment(JSON.stringify(payload));
  const signature = signSegment(encodedPayload);

  return {
    token: `${encodedPayload}.${signature}`,
    expiresAt: new Date(expiresAtTimestamp).toISOString(),
  };
};

export const verifyPatientSessionToken = (token: string): VerifiedPatientSession | null => {
  const [encodedPayload, providedSignature] = token.split('.');

  if (!encodedPayload || !providedSignature) {
    return null;
  }

  const expectedSignature = signSegment(encodedPayload);
  const expectedBuffer = Buffer.from(expectedSignature);
  const providedBuffer = Buffer.from(providedSignature);

  if (expectedBuffer.length !== providedBuffer.length) {
    return null;
  }

  if (!timingSafeEqual(expectedBuffer, providedBuffer)) {
    return null;
  }

  try {
    const payload = JSON.parse(decodeSegment(encodedPayload)) as Partial<RawPatientSessionPayload>;

    if (
      payload.v !== 1 ||
      typeof payload.acc !== 'string' ||
      typeof payload.pat !== 'string' ||
      typeof payload.email !== 'string' ||
      typeof payload.exp !== 'number'
    ) {
      return null;
    }

    if (payload.exp <= Date.now()) {
      return null;
    }

    return {
      accountId: payload.acc,
      patientId: payload.pat,
      email: payload.email,
      expiresAt: new Date(payload.exp).toISOString(),
    };
  } catch {
    return null;
  }
};
