import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid';
import { verifyEvent, type VerifiedEvent, nip19 } from 'nostr-tools';
import { prisma } from '../db/prisma';
import {
  ALL_PERMISSION_KEYS,
  isAssignableRole,
  type EffectiveRole,
} from '../constants/permissions';

const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production';
const CHALLENGE_TTL_MS = 5 * 60 * 1000;

export interface ResolvedAccess {
  pubkey: string;
  role: EffectiveRole;
  isSuperAdmin: boolean;
  permissions: Set<string>;
}

// Reads the SuperAdmin pubkey list from the environment, decoding npub to hex.
// Falls back to the legacy ADMIN_PUBKEYS variable so existing deployments keep
// working. The env-admin concept is now SuperAdmin.
function getSuperadminPubkeys(): string[] {
  const raw = process.env.SUPERADMIN_PUBKEYS ?? process.env.ADMIN_PUBKEYS ?? '';
  return raw
    .split(',')
    .map((p) => p.trim())
    .filter(Boolean)
    .map((p) => {
      if (p.startsWith('npub1')) {
        try {
          const { data } = nip19.decode(p);
          return data as string;
        } catch {
          return p;
        }
      }
      return p;
    });
}

export function isSuperadmin(pubkey: string): boolean {
  return getSuperadminPubkeys().includes(pubkey);
}

interface StoredChallenge {
  challenge: string;
  expiresAt: number;
}

const challenges = new Map<string, StoredChallenge>();

// Periodically clean up expired challenges
setInterval(() => {
  const now = Date.now();
  for (const [key, value] of challenges) {
    if (value.expiresAt < now) {
      challenges.delete(key);
    }
  }
}, 60_000);

export const authService = {
  createChallenge(pubkey: string): string {
    const challenge = uuidv4();
    challenges.set(pubkey, {
      challenge,
      expiresAt: Date.now() + CHALLENGE_TTL_MS,
    });
    return challenge;
  },

  verifySignature(pubkey: string, signedEvent: VerifiedEvent): boolean {
    const stored = challenges.get(pubkey);
    if (!stored) return false;
    if (stored.expiresAt < Date.now()) {
      challenges.delete(pubkey);
      return false;
    }

    // Verify the event signature
    if (!verifyEvent(signedEvent)) return false;

    // Kind 22242 is the NIP-42 auth kind
    if (signedEvent.kind !== 22242) return false;
    if (signedEvent.pubkey !== pubkey) return false;

    // Check that the challenge tag matches
    const challengeTag = signedEvent.tags.find(
      (t) => t[0] === 'challenge'
    );
    if (!challengeTag || challengeTag[1] !== stored.challenge) return false;

    challenges.delete(pubkey);
    return true;
  },

  generateToken(pubkey: string, role: string): string {
    return jwt.sign({ pubkey, role }, JWT_SECRET, { expiresIn: '7d' });
  },

  isSuperadmin,

  // Resolves the effective role and permission set for a pubkey, live, from the
  // env SuperAdmin list plus the database. This is the authoritative source for
  // authorization. The role baked into a JWT is only used for display.
  async resolveAccess(pubkey: string): Promise<ResolvedAccess> {
    if (isSuperadmin(pubkey)) {
      return {
        pubkey,
        role: 'superadmin',
        isSuperAdmin: true,
        permissions: new Set(ALL_PERMISSION_KEYS),
      };
    }

    const user = await prisma.user.findUnique({ where: { pubkey } });
    const role: EffectiveRole = isAssignableRole(user?.role) ? user!.role : 'guest';

    if (role === 'guest') {
      return { pubkey, role, isSuperAdmin: false, permissions: new Set() };
    }

    const rows = await prisma.rolePermission.findMany({ where: { role } });
    return {
      pubkey,
      role,
      isSuperAdmin: false,
      permissions: new Set(rows.map((r) => r.permission)),
    };
  },
};
