import crypto from 'node:crypto';
import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose';
import { config } from './config.js';
import { queryOne } from './db.js';
import { getHeader, type RequestContext } from './http.js';

export interface SessionIdentity {
  id: string;
  username: string | null;
  phone: string | null;
  name: string | null;
  avatarUrl: string | null;
  primaryRole: string;
  createdAt: string;
  tenantId: string | null;
  sessionId: string;
  sessionExpiresAt: string;
  authSource: 'app_session' | 'supabase_jwt';
  authUserId: string | null;
  platformPermissions: Record<string, unknown>;
}

interface RequestAuthState {
  bearerToken: string;
  session: SessionIdentity | null;
  sessionResolved: boolean;
}

const requestAuthState = new WeakMap<RequestContext, RequestAuthState>();
let remoteJwks: ReturnType<typeof createRemoteJWKSet> | null = null;

export function bearerTokenFrom(ctx: RequestContext) {
  const authorization = getHeader(ctx.req, 'authorization');
  const match = authorization.match(/^Bearer\s+(.+)$/i);
  return match?.[1]?.trim() || '';
}

function tenantContextFrom(ctx: RequestContext) {
  return getHeader(ctx.req, 'x-tenant-id') || ctx.url.searchParams.get('tenantId') || '';
}

export function hashSessionToken(token: string) {
  return crypto.createHmac('sha256', config.authSessionSecret).update(token).digest('hex');
}

export async function findUserBySessionToken(token: string) {
  const tokenHash = hashSessionToken(token);
  return queryOne<SessionIdentity>(
    `
    select u.id, u.username, u.phone, u.name, u.avatar_url as "avatarUrl",
           u.primary_role as "primaryRole", u.created_at as "createdAt",
           s.tenant_id as "tenantId", s.id as "sessionId", s.expires_at as "sessionExpiresAt",
           'app_session'::text as "authSource",
           u.auth_user_id as "authUserId",
           u.platform_permissions as "platformPermissions"
    from app_private.auth_sessions s
    join public.platform_users u on u.id = s.user_id
    where s.token_hash = $1
      and u.status = 'active'
      and s.revoked_at is null
      and s.expires_at > now()
    limit 1
    `,
    [tokenHash],
  );
}

function expectedIssuer() {
  return config.authJwtIssuer || undefined;
}

function expectedAudience() {
  return config.authJwtAudience || undefined;
}

function symmetricJwtSecret() {
  return new TextEncoder().encode(config.authJwtSecret);
}

function jwksKeySet() {
  if (!config.authJwtJwksUrl) return null;
  if (!remoteJwks) remoteJwks = createRemoteJWKSet(new URL(config.authJwtJwksUrl));
  return remoteJwks;
}

async function verifySupabaseJwt(token: string) {
  const verifyOptions = {
    issuer: expectedIssuer(),
    audience: expectedAudience(),
  };
  const jwks = jwksKeySet();
  const verified = jwks
    ? await jwtVerify(token, jwks, verifyOptions)
    : await jwtVerify(token, symmetricJwtSecret(), verifyOptions);
  return verified.payload;
}

function objectClaim(payload: JWTPayload, key: string) {
  const value = payload[key];
  return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function tenantClaimFrom(payload: JWTPayload) {
  const claim = payload.tenant_id || objectClaim(payload, 'app_metadata').tenant_id || objectClaim(payload, 'user_metadata').tenant_id;
  return typeof claim === 'string' && claim.trim() ? claim.trim() : '';
}

function appRoleClaimFrom(payload: JWTPayload) {
  const claim = payload.app_role || objectClaim(payload, 'app_metadata').app_role || payload.role;
  return typeof claim === 'string' && claim.trim() ? claim.trim() : '';
}

function sessionExpiryFrom(payload: JWTPayload) {
  return payload.exp ? new Date(payload.exp * 1000).toISOString() : new Date(Date.now() + 60_000).toISOString();
}

export async function findUserBySupabaseJwt(token: string, requestedTenantContext = '') {
  let payload: JWTPayload;
  try {
    payload = await verifySupabaseJwt(token);
  } catch {
    return null;
  }

  const authUserId = typeof payload.sub === 'string' && payload.sub ? payload.sub : '';
  if (!authUserId) return null;

  const tenantClaim = tenantClaimFrom(payload);
  if (tenantClaim && requestedTenantContext && tenantClaim !== requestedTenantContext) return null;
  const requestedTenantId = tenantClaim || requestedTenantContext;
  const appRole = appRoleClaimFrom(payload);

  const platformUser = await queryOne<SessionIdentity>(
    `
    select u.id, u.username, u.phone, u.name, u.avatar_url as "avatarUrl",
           u.primary_role as "primaryRole", u.created_at as "createdAt",
           coalesce($2::uuid, tm.tenant_id) as "tenantId",
           $1::text as "sessionId",
           $3::timestamptz as "sessionExpiresAt",
           'supabase_jwt'::text as "authSource",
           u.auth_user_id as "authUserId",
           u.platform_permissions as "platformPermissions"
    from public.platform_users u
    left join public.tenant_memberships tm on tm.user_id = u.id and tm.status = 'active'
    where u.auth_user_id = $1::uuid
      and u.status = 'active'
      and u.primary_role = 'platform_admin'
      and ($2::uuid is null or exists (
        select 1
        from public.tenant_memberships scoped_tm
        where scoped_tm.user_id = u.id
          and scoped_tm.tenant_id = $2::uuid
          and scoped_tm.status = 'active'
      ))
    order by tm.created_at asc nulls last
    limit 1
    `,
    [authUserId, requestedTenantId || null, sessionExpiryFrom(payload)],
  );

  if (platformUser && (!appRole || appRole === 'platform_admin' || appRole === 'service_role')) {
    return platformUser;
  }

  if (!requestedTenantId) return null;

  return queryOne<SessionIdentity>(
    `
    select u.id, u.username, u.phone, u.name, u.avatar_url as "avatarUrl",
           u.primary_role as "primaryRole", u.created_at as "createdAt",
           tm.tenant_id as "tenantId",
           $1::text as "sessionId",
           $3::timestamptz as "sessionExpiresAt",
           'supabase_jwt'::text as "authSource",
           u.auth_user_id as "authUserId",
           u.platform_permissions as "platformPermissions"
    from public.platform_users u
    join public.tenant_memberships tm on tm.user_id = u.id
    where u.auth_user_id = $1::uuid
      and u.status = 'active'
      and tm.status = 'active'
      and tm.tenant_id = $2::uuid
    order by case
      when u.primary_role = 'platform_admin' then 0
      when tm.role = 'tenant_owner' then 1
      when tm.role = 'tenant_admin' then 2
      else 9
    end, tm.created_at asc
    limit 1
    `,
    [authUserId, requestedTenantId, sessionExpiryFrom(payload)],
  );
}

export async function hydrateRequestAuth(ctx: RequestContext) {
  const cached = requestAuthState.get(ctx);
  if (cached?.sessionResolved) return cached;

  const bearerToken = bearerTokenFrom(ctx);
  let session: SessionIdentity | null = null;
  if (bearerToken) {
    session = bearerToken.startsWith('tk_')
      ? await findUserBySessionToken(bearerToken)
      : await findUserBySupabaseJwt(bearerToken, tenantContextFrom(ctx));
  }
  const state = { bearerToken, session, sessionResolved: true };
  requestAuthState.set(ctx, state);
  return state;
}

export function currentSessionFromContext(ctx: RequestContext) {
  return requestAuthState.get(ctx)?.session || null;
}

export function hasInvalidBearerSession(ctx: RequestContext) {
  const state = requestAuthState.get(ctx);
  return Boolean(state?.bearerToken && !state.session);
}
