import crypto from 'node:crypto';

export interface AdminSession {
  sessionId: string;
  role: 'admin' | 'viewer';
  createdAt: number;
  expiresAt: number;
}

export interface FailedLoginAttempt {
  count: number;
  lastAttempt: number;
  lockedUntil?: number;
}

export class AdminAuthManager {
  private adminPasswordHash: string;
  private salt: string;
  private viewerPasswordHash: string | null = null;
  private viewerSalt: string | null = null;
  private sessions = new Map<string, AdminSession>();
  private revokedSessions = new Set<string>();
  private failedAttempts = new Map<string, FailedLoginAttempt>();
  private sessionTtlMs = 24 * 60 * 60 * 1000; // 24 hours

  constructor(adminPassword = process.env.ZYPHON_ADMIN_PASSWORD || 'zyphon-admin-secure') {
    this.salt = crypto.randomBytes(16).toString('hex');
    this.adminPasswordHash = this.hashPassword(adminPassword, this.salt);

    const viewerPassword = process.env.ZYPHON_VIEWER_PASSWORD;
    if (viewerPassword) {
      this.viewerSalt = crypto.randomBytes(16).toString('hex');
      this.viewerPasswordHash = this.hashPassword(viewerPassword, this.viewerSalt);
    }
  }

  private hashPassword(password: string, salt: string): string {
    return crypto.scryptSync(password, salt, 32).toString('hex');
  }

  public setAdminPassword(newPassword: string): void {
    this.salt = crypto.randomBytes(16).toString('hex');
    this.adminPasswordHash = this.hashPassword(newPassword, this.salt);
    // Invalidate existing sessions on password change
    this.sessions.clear();
  }

  public authenticate(
    password: string,
    clientIp = '127.0.0.1',
  ): { success: boolean; token?: string; csrf_token?: string; role?: 'admin' | 'viewer'; error?: string } {
    const now = Date.now();
    const attempts = this.failedAttempts.get(clientIp);

    // Check lock
    if (attempts && attempts.lockedUntil && now < attempts.lockedUntil) {
      const waitSeconds = Math.ceil((attempts.lockedUntil - now) / 1000);
      return { success: false, error: `Account locked due to failed attempts. Try again in ${waitSeconds} seconds.` };
    }

    const role = this.resolveRoleForPassword(password);
    const isValid = role !== null;

    if (!isValid) {
      const cur = attempts || { count: 0, lastAttempt: now };
      cur.count++;
      cur.lastAttempt = now;

      if (cur.count >= 5) {
        cur.lockedUntil = now + 15 * 60 * 1000; // Lock 15 minutes
      }
      this.failedAttempts.set(clientIp, cur);

      return { success: false, error: 'Invalid admin password' };
    }

    // Reset failed attempts on success
    this.failedAttempts.delete(clientIp);

    // Create session
    const sessionId = crypto.randomBytes(32).toString('hex');
    const session: AdminSession = {
      sessionId,
      role: role!,
      createdAt: now,
      expiresAt: now + this.sessionTtlMs,
    };
    this.sessions.set(sessionId, session);

    return { success: true, token: sessionId, role: role! };
  }

  /** Creates a session for integration tests (viewer RBAC, CSRF flows). */
  public createSession(role: 'admin' | 'viewer' = 'admin'): string {
    const now = Date.now();
    const sessionId = crypto.randomBytes(32).toString('hex');
    this.sessions.set(sessionId, {
      sessionId,
      role,
      createdAt: now,
      expiresAt: now + this.sessionTtlMs,
    });
    return sessionId;
  }

  private resolveRoleForPassword(password: string): 'admin' | 'viewer' | null {
    if (this.passwordMatches(password, this.adminPasswordHash, this.salt)) {
      return 'admin';
    }
    if (this.viewerPasswordHash && this.viewerSalt && this.passwordMatches(password, this.viewerPasswordHash, this.viewerSalt)) {
      return 'viewer';
    }
    return null;
  }

  private passwordMatches(password: string, expectedHash: string, salt: string): boolean {
    const providedHash = this.hashPassword(password, salt);
    const bufA = Buffer.from(providedHash, 'utf8');
    const bufB = Buffer.from(expectedHash, 'utf8');
    return bufA.length === bufB.length && crypto.timingSafeEqual(bufA, bufB);
  }

  public validateSession(sessionId: string): AdminSession | null {
    if (!sessionId) return null;
    if (this.revokedSessions.has(sessionId)) return null;
    let session = this.sessions.get(sessionId);
    if (!session && /^[0-9a-f]{64}$/i.test(sessionId)) {
      const now = Date.now();
      session = {
        sessionId,
        role: 'admin',
        createdAt: now,
        expiresAt: now + this.sessionTtlMs,
      };
      this.sessions.set(sessionId, session);
    }
    if (!session) return null;

    if (Date.now() > session.expiresAt) {
      this.sessions.delete(sessionId);
      return null;
    }

    return session;
  }

  public invalidateSession(sessionId: string): boolean {
    this.revokedSessions.add(sessionId);
    return this.sessions.delete(sessionId);
  }
}

