import * as plugins from '../../plugins.ts';
import { logger } from '../../logging.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { hashPassword, verifyPassword } from '../../utils/auth.ts';

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

export class AdminHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();
  public smartjwtInstance!: plugins.smartjwt.SmartJwt<IJwtData>;

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

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

  private async createIdentityForUser(
    user: interfaces.data.IUser & { id?: number },
    expiresAt: number,
  ): Promise<interfaces.data.IIdentity> {
    const userId = String(user.id || user.username);
    const jwt = await this.smartjwtInstance.createJWT({
      userId,
      username: user.username,
      role: user.role,
      status: 'loggedIn',
      expiresAt,
    });

    return {
      jwt,
      userId,
      username: user.username,
      expiresAt,
      role: user.role,
    };
  }

  public async getVerifiedIdentity(
    identityArg: interfaces.data.IIdentity | null | undefined,
  ): Promise<interfaces.data.IIdentity> {
    if (!identityArg?.jwt) {
      throw new plugins.typedrequest.TypedResponseError('No identity provided');
    }

    let jwtData: IJwtData;
    try {
      jwtData = await this.smartjwtInstance.verifyJWTAndGetData(identityArg.jwt);
    } catch {
      throw new plugins.typedrequest.TypedResponseError('Valid identity required');
    }

    if (jwtData.expiresAt < Date.now() || jwtData.status !== 'loggedIn') {
      throw new plugins.typedrequest.TypedResponseError('Valid identity required');
    }

    const user = this.opsServerRef.oneboxRef.database.getUserByUsername(jwtData.username);
    if (!user) {
      throw new plugins.typedrequest.TypedResponseError('Valid identity required');
    }

    const userId = String(user.id || user.username);
    if (jwtData.userId !== userId) {
      throw new plugins.typedrequest.TypedResponseError('Valid identity required');
    }

    return {
      jwt: identityArg.jwt,
      userId,
      username: user.username,
      expiresAt: jwtData.expiresAt,
      role: user.role,
    };
  }

  public async getVerifiedAdminIdentity(
    identityArg: interfaces.data.IIdentity | null | undefined,
  ): Promise<interfaces.data.IIdentity> {
    const identity = await this.getVerifiedIdentity(identityArg);
    if (identity.role !== 'admin') {
      throw new plugins.typedrequest.TypedResponseError('Admin access required');
    }
    return identity;
  }

  private registerHandlers(): void {
    // Login
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
        'adminLoginWithUsernameAndPassword',
        async (dataArg) => {
          try {
            const user = this.opsServerRef.oneboxRef.database.getUserByUsername(dataArg.username);
            if (!user) {
              throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
            }

            const passwordMatches = await verifyPassword(dataArg.password, user.passwordHash);
            if (!passwordMatches) {
              throw new plugins.typedrequest.TypedResponseError('Invalid credentials');
            }

            const expiresAt = Date.now() + 24 * 3600 * 1000;
            const freshUser = this.opsServerRef.oneboxRef.database.getUserByUsername(user.username) || user;
            const identity = await this.createIdentityForUser(freshUser, expiresAt);

            logger.info(`User logged in: ${user.username}`);

            return {
              identity,
            };
          } catch (error) {
            if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
            throw new plugins.typedrequest.TypedResponseError('Login failed');
          }
        },
      ),
    );

    // Logout
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLogout>(
        'adminLogout',
        async (_dataArg) => {
          return { ok: true };
        },
      ),
    );

    // Verify Identity
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
        'verifyIdentity',
        async (dataArg) => {
          try {
            const identity = await this.getVerifiedIdentity(dataArg.identity);
            return {
              valid: true,
              identity,
            };
          } catch {
            return { valid: false };
          }
        },
      ),
    );

    // Change Password
    this.typedrouter.addTypedHandler(
        new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ChangePassword>(
          'changePassword',
          async (dataArg) => {
          const identity = await this.getVerifiedIdentity(dataArg.identity);
          const user = this.opsServerRef.oneboxRef.database.getUserByUsername(identity.username);
          if (!user) {
            throw new plugins.typedrequest.TypedResponseError('User not found');
          }

          const currentPasswordMatches = await verifyPassword(dataArg.currentPassword, user.passwordHash);
          if (!currentPasswordMatches) {
            throw new plugins.typedrequest.TypedResponseError('Current password is incorrect');
          }

          const newHash = await hashPassword(dataArg.newPassword);
          this.opsServerRef.oneboxRef.database.updateUserPassword(user.username, newHash);
          logger.info(`Password changed for user: ${user.username}`);

          return { ok: true };
        },
      ),
    );
  }

  // Guard for valid identity
  public validIdentityGuard = new plugins.smartguard.Guard<{
    identity: interfaces.data.IIdentity;
  }>(
    async (dataArg) => {
      try {
        await this.getVerifiedIdentity(dataArg.identity);
        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) => {
      try {
        const identity = await this.getVerifiedIdentity(dataArg.identity);
        return identity.role === 'admin';
      } catch {
        return false;
      }
    },
    { failedHint: 'user is not admin', name: 'adminIdentityGuard' },
  );
}
