import * as plugins from '../../plugins.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';

export interface IJwtData {
  userId: string;
  role: 'admin';
  status: 'loggedIn' | 'loggedOut';
  expiresAt: number;
}

export class AdminHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();
  public smartjwtInstance!: plugins.smartjwt.SmartJwt<IJwtData>;
  private revokedTokens = new Set<string>();
  private failedLoginAttempts = new Map<string, { count: number; firstAttemptAt: number }>();

  constructor(private opsServerRef: OpsServer) {
    this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
  }

  public async initialize(): Promise<void> {
    this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
    await this.smartjwtInstance.init();
    await this.smartjwtInstance.createNewKeyPair();
    this.registerHandlers();
  }

  private registerHandlers(): void {
    // Login
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
        'adminLoginWithUsernameAndPassword',
        async (dataArg) => {
          this.assertLoginNotRateLimited(dataArg.username);
          const adminPassword = this.opsServerRef.objectStorageRef.config.adminPassword;
          if (dataArg.username !== 'admin' || dataArg.password !== adminPassword) {
            this.recordFailedLogin(dataArg.username);
            await this.opsServerRef.objectStorageRef.auditLogger.log({
              actorUserId: dataArg.username || 'anonymous',
              action: 'admin.login',
              targetType: 'adminSession',
              success: false,
              message: 'Invalid credentials',
            });
            throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
          }

          this.failedLoginAttempts.delete(dataArg.username);
          const expiresAt = Date.now() + 24 * 3600 * 1000;
          const userId = 'admin';
          const jwt = await this.smartjwtInstance.createJWT({
            userId,
            role: 'admin',
            status: 'loggedIn',
            expiresAt,
          });

          await this.opsServerRef.objectStorageRef.auditLogger.log({
            actorUserId: userId,
            action: 'admin.login',
            targetType: 'adminSession',
            success: true,
          });

          console.log('Admin user logged in');

          return {
            identity: {
              jwt,
              userId,
              username: 'admin',
              expiresAt,
              role: 'admin' as const,
            },
          };
        },
      ),
    );

    // Logout
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLogout>(
        'adminLogout',
        async (dataArg) => {
          if (dataArg.identity?.jwt) {
            this.revokedTokens.add(dataArg.identity.jwt);
            await this.opsServerRef.objectStorageRef.auditLogger.log({
              actorUserId: dataArg.identity.userId,
              action: 'admin.logout',
              targetType: 'adminSession',
              success: true,
            });
          }
          return { ok: true };
        },
      ),
    );

    // Verify Identity
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
        'verifyIdentity',
        async (dataArg) => {
          if (!dataArg.identity?.jwt) {
            return { valid: false };
          }
          if (this.revokedTokens.has(dataArg.identity.jwt)) return { valid: false };
          try {
            const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
            if (jwtData.expiresAt < Date.now()) return { valid: false };
            if (jwtData.status !== 'loggedIn') return { valid: false };
            if (jwtData.role !== 'admin') return { valid: false };
            return {
              valid: true,
              identity: {
                jwt: dataArg.identity.jwt,
                userId: jwtData.userId,
                username: jwtData.userId,
                expiresAt: jwtData.expiresAt,
                role: jwtData.role,
              },
            };
          } catch {
            return { valid: false };
          }
        },
      ),
    );
  }

  // Guard for valid identity
  public validIdentityGuard = new plugins.smartguard.Guard<{
    identity: interfaces.data.IIdentity;
  }>(
    async (dataArg) => {
      if (!dataArg.identity?.jwt) return false;
      if (this.revokedTokens.has(dataArg.identity.jwt)) return false;
      try {
        const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
        if (jwtData.expiresAt < Date.now()) return false;
        if (jwtData.status !== 'loggedIn') return false;
        if (jwtData.role !== 'admin') return false;
        if (dataArg.identity.expiresAt !== jwtData.expiresAt) return false;
        if (dataArg.identity.userId !== jwtData.userId) return false;
        if (dataArg.identity.role !== jwtData.role) return false;
        return true;
      } catch {
        return false;
      }
    },
    { failedHint: 'identity is not valid', name: 'validIdentityGuard' },
  );

  // Guard for admin identity
  public adminIdentityGuard = new plugins.smartguard.Guard<{
    identity: interfaces.data.IIdentity;
  }>(
    async (dataArg) => {
      return await this.validIdentityGuard.exec(dataArg);
    },
    { failedHint: 'user is not admin', name: 'adminIdentityGuard' },
  );

  private assertLoginNotRateLimited(username: string): void {
    const attempt = this.failedLoginAttempts.get(username);
    if (!attempt) return;

    const windowMs = 60 * 1000;
    if (Date.now() - attempt.firstAttemptAt > windowMs) {
      this.failedLoginAttempts.delete(username);
      return;
    }

    if (attempt.count >= 5) {
      throw new plugins.typedrequest.TypedResponseError('Too many failed login attempts');
    }
  }

  private recordFailedLogin(username: string): void {
    const now = Date.now();
    const attempt = this.failedLoginAttempts.get(username);
    if (!attempt || now - attempt.firstAttemptAt > 60 * 1000) {
      this.failedLoginAttempts.set(username, { count: 1, firstAttemptAt: now });
      return;
    }
    attempt.count++;
  }
}
