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

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

type TAdminUser = {
  id: string;
  username: string;
  email?: string;
  name?: string;
  role: string;
  status?: 'active' | 'disabled';
  authSources?: Array<'local' | 'idp.global'>;
};

export class AdminHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();
  
  // JWT instance
  public smartjwtInstance!: plugins.smartjwt.SmartJwt<IJwtData>;
  
  // Ephemeral bootstrap users. DB-backed instances may use these only until the
  // database is ready and the first persistent admin account has been created.
  private users = new Map<string, {
    id: string;
    username: string;
    password: string;
    role: string;
  }>();

  private accountStore?: plugins.idpSdkServer.SmartdataAccountStore;
  private idpClient?: plugins.idpSdkServer.IdpGlobalServerClient;
  private ownsIdpClient = false;
  
  constructor(private opsServerRef: OpsServer) {
    // Add this handler's router to the parent
    this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
  }
  
  public async initialize(): Promise<void> {
    await this.initializeJwt();
    this.initializeDefaultUsers();
    this.registerHandlers();
  }

  public async stop(): Promise<void> {
    if (this.ownsIdpClient) {
      await this.idpClient?.stop();
    }
    this.idpClient = undefined;
    this.ownsIdpClient = false;
  }
  
  private async initializeJwt(): Promise<void> {
    this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
    await this.smartjwtInstance.init();
    
    // For development, create new keypair each time
    // In production, load from storage like cloudly does
    await this.smartjwtInstance.createNewKeyPair();
  }
  
  private initializeDefaultUsers(): void {
    const username = process.env.DCROUTER_ADMIN_USERNAME || 'admin';
    const configuredPassword = process.env.DCROUTER_ADMIN_PASSWORD;
    const password = configuredPassword || plugins.crypto.randomBytes(24).toString('base64url');

    const adminId = plugins.uuid.v4();
    this.users.set(adminId, {
      id: adminId,
      username,
      password,
      role: 'admin',
    });

    if (!configuredPassword) {
      console.warn(`DCRouter generated one-time admin password for ${username}: ${password}`);
    }
  }

  /**
   * Return a safe projection of the active user source — excludes password fields.
   * Used by UsersHandler to serve the admin-only listUsers endpoint.
   */
  public async listUsers(): Promise<interfaces.requests.IAdminUserProjection[]> {
    const accountState = await this.getPersistentAccountState();
    if (accountState.dbEnabled && !accountState.dbReady) {
      throw new plugins.typedrequest.TypedResponseError('database is not ready');
    }
    if (accountState.hasPersistentAdmin) {
      const accounts = await accountState.store!.listAccounts();
      return accounts.map((accountArg) => this.accountToUser(accountArg));
    }

    return Array.from(this.users.values()).map((user) => ({
      id: user.id,
      username: user.username,
      role: user.role,
    }));
  }

  public async getBootstrapStatus(): Promise<interfaces.requests.IReq_GetAdminBootstrapStatus['response']> {
    const accountState = await this.getPersistentAccountState();
    const bootstrapAvailable = !accountState.dbEnabled || (accountState.dbReady && !accountState.hasPersistentAdmin);
    return {
      dbEnabled: accountState.dbEnabled,
      dbReady: accountState.dbReady,
      hasPersistentAdmin: accountState.hasPersistentAdmin,
      needsBootstrap: accountState.dbEnabled && accountState.dbReady && !accountState.hasPersistentAdmin,
      ephemeralAdminAvailable: bootstrapAvailable,
      idpGlobalConfigured: this.isIdpGlobalConfigured(),
    };
  }

  public async createInitialAdminUser(optionsArg: {
    email: string;
    name?: string;
    password: string;
    enableIdpGlobalAuth?: boolean;
  }): Promise<interfaces.requests.IReq_CreateInitialAdminUser['response']> {
    const store = this.getAccountStore();
    if (!store) {
      throw new plugins.typedrequest.TypedResponseError('database is not ready');
    }

    if (await store.hasActiveAdminAccount()) {
      throw new plugins.typedrequest.TypedResponseError('initial admin already exists');
    }

    const password = String(optionsArg.password || '');
    if (!password) {
      throw new plugins.typedrequest.TypedResponseError('password is required');
    }

    const email = String(optionsArg.email || '').trim();
    const authSources: Array<'local' | 'idp.global'> = ['local'];
    if (optionsArg.enableIdpGlobalAuth) {
      authSources.push('idp.global');
    }

    try {
      const account = await store.createAccount({
        email,
        name: String(optionsArg.name || '').trim() || email,
        role: 'admin',
        authSources,
        password,
      });
      const user = this.accountToUser(account);
      return {
        success: true,
        identity: await this.createIdentityForUser(user),
        user,
      };
    } catch (error) {
      throw new plugins.typedrequest.TypedResponseError((error as Error).message || 'failed to create initial admin');
    }
  }

  public async createUser(optionsArg: {
    email: string;
    name?: string;
    role: interfaces.requests.TUserManagementRole;
    password: string;
    enableIdpGlobalAuth?: boolean;
  }): Promise<interfaces.requests.IReq_CreateUser['response']> {
    const store = this.getAccountStore();
    if (!store) {
      return { success: false, message: 'database is not ready' };
    }
    if (!(await store.hasActiveAdminAccount())) {
      return { success: false, message: 'initial admin bootstrap is required before creating users' };
    }

    const role = optionsArg.role;
    if (role !== 'admin' && role !== 'user') {
      return { success: false, message: 'role must be admin or user' };
    }

    const password = String(optionsArg.password || '');
    if (!password) {
      return { success: false, message: 'password is required' };
    }

    const authSources: Array<'local' | 'idp.global'> = ['local'];
    if (optionsArg.enableIdpGlobalAuth) {
      authSources.push('idp.global');
    }

    try {
      const email = String(optionsArg.email || '').trim();
      const account = await store.createAccount({
        email,
        name: String(optionsArg.name || '').trim() || email,
        role,
        authSources,
        password,
      });
      return { success: true, user: this.accountToUser(account) };
    } catch (error) {
      return { success: false, message: (error as Error).message || 'failed to create user' };
    }
  }

  public async deleteUser(optionsArg: {
    id: string;
    requestingUserId: string;
  }): Promise<interfaces.requests.IReq_DeleteUser['response']> {
    const store = this.getAccountStore();
    if (!store) {
      return { success: false, message: 'database is not ready' };
    }
    if (!(await store.hasActiveAdminAccount())) {
      return { success: false, message: 'initial admin bootstrap is required before deleting users' };
    }

    const id = String(optionsArg.id || '').trim();
    if (!id) {
      return { success: false, message: 'user id is required' };
    }
    if (id === optionsArg.requestingUserId) {
      return { success: false, message: 'cannot delete the current user' };
    }

    const account = await store.getAccountById(id);
    if (!account) {
      return { success: false, message: 'user not found' };
    }

    if (account.role === 'admin' && account.status === 'active') {
      const activeAdmins = (await store.listAccounts()).filter(
        (accountArg) => accountArg.role === 'admin' && accountArg.status === 'active',
      );
      if (activeAdmins.length <= 1) {
        return { success: false, message: 'cannot delete the last active admin' };
      }
    }

    const doc = await plugins.idpSdkServer.IdpSdkAccountDoc.findById(id);
    if (!doc) {
      return { success: false, message: 'user not found' };
    }
    await doc.delete();
    return { success: true };
  }
  
  private registerHandlers(): void {
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAdminBootstrapStatus>(
        'getAdminBootstrapStatus',
        async (_dataArg) => this.getBootstrapStatus()
      )
    );

    this.opsServerRef.adminRouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateInitialAdminUser>(
        'createInitialAdminUser',
        async (dataArg) => {
          const isAdmin = await this.adminIdentityGuard.exec({ identity: dataArg.identity });
          if (!isAdmin) {
            throw new plugins.typedrequest.TypedResponseError('admin identity required');
          }
          return this.createInitialAdminUser({
            email: dataArg.email,
            name: dataArg.name,
            password: dataArg.password,
            enableIdpGlobalAuth: dataArg.enableIdpGlobalAuth,
          });
        }
      )
    );

    // Admin Login Handler
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
        'adminLoginWithUsernameAndPassword',
        async (dataArg) => {
          try {
            const user = await this.authenticateUser({
              username: dataArg.username,
              password: dataArg.password,
              authSource: dataArg.authSource,
            });
            if (!user) {
              throw new plugins.typedrequest.TypedResponseError('login failed');
            }

            return {
              identity: await this.createIdentityForUser(user),
            };
          } catch (error) {
            if (error instanceof plugins.typedrequest.TypedResponseError) {
              throw error;
            }
            throw new plugins.typedrequest.TypedResponseError('login failed');
          }
        }
      )
    );
    
    // Admin Logout Handler
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLogout>(
        'adminLogout',
        async (dataArg) => {
          const identity = await this.validateIdentity(dataArg.identity);
          if (!identity) {
            throw new plugins.typedrequest.TypedResponseError('identity is not valid');
          }
          return {
            success: true,
          };
        }
      )
    );
    
    // Verify Identity Handler
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
        'verifyIdentity',
        async (dataArg) => {
          const identity = await this.validateIdentity(dataArg.identity);
          return identity ? { valid: true, identity } : { valid: false };
        }
      )
    );
  }
  
  /**
   * Create a guard for valid identity (matching cloudly pattern)
   */
  public validIdentityGuard = new plugins.smartguard.Guard<{
    identity: interfaces.data.IIdentity;
  }>(
    async (dataArg) => {
      return Boolean(await this.validateIdentity(dataArg.identity));
    },
    {
      failedHint: 'identity is not valid',
      name: 'validIdentityGuard',
    }
  );
  
  /**
   * Create a guard for admin identity (matching cloudly pattern)
   */
  public adminIdentityGuard = new plugins.smartguard.Guard<{
    identity: interfaces.data.IIdentity;
  }>(
    async (dataArg) => {
      const identity = await this.validateIdentity(dataArg.identity);
      return identity?.role === 'admin';
    },
    {
      failedHint: 'user is not admin',
      name: 'adminIdentityGuard',
    }
  );

  public async validateIdentity(
    identityArg?: interfaces.data.IIdentity,
  ): Promise<interfaces.data.IIdentity | null> {
    if (!identityArg?.jwt) {
      return null;
    }

    try {
      const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(identityArg.jwt);
      if (jwtData.expiresAt < Date.now()) {
        return null;
      }
      if (jwtData.status !== 'loggedIn') {
        return null;
      }
      if (identityArg.expiresAt !== jwtData.expiresAt) {
        return null;
      }
      if (identityArg.userId !== jwtData.userId) {
        return null;
      }

      const user = await this.resolveUser(jwtData.userId);
      if (!user) {
        return null;
      }
      if (identityArg.role && identityArg.role !== user.role) {
        return null;
      }

      return {
        jwt: identityArg.jwt,
        userId: user.id,
        name: user.name || user.username,
        expiresAt: jwtData.expiresAt,
        role: user.role,
        type: 'user',
      };
    } catch {
      return null;
    }
  }

  private async authenticateUser(optionsArg: {
    username: string;
    password: string;
    authSource?: interfaces.requests.TAdminLoginAuthSource;
  }): Promise<TAdminUser | null> {
    const accountState = await this.getPersistentAccountState();
    if (accountState.dbEnabled && !accountState.dbReady) {
      throw new plugins.typedrequest.TypedResponseError('database is not ready');
    }

    if (accountState.hasPersistentAdmin) {
      const authService = new plugins.idpSdkServer.AccountAuthService({
        store: accountState.store!,
        idpClient: this.getIdpClient() as plugins.idpSdkServer.IdpGlobalServerClient | undefined,
      });
      const result = await authService.authenticate({
        email: optionsArg.username,
        password: optionsArg.password,
        authSource: optionsArg.authSource || 'auto',
      });
      return result ? this.accountToUser(result.account) : null;
    }

    for (const [_, userData] of this.users) {
      if (userData.username === optionsArg.username && userData.password === optionsArg.password) {
        return userData;
      }
    }
    return null;
  }

  private async resolveUser(userIdArg: string): Promise<TAdminUser | null> {
    const accountState = await this.getPersistentAccountState();
    if (accountState.dbEnabled && !accountState.dbReady) {
      return null;
    }

    if (accountState.hasPersistentAdmin) {
      const account = await accountState.store!.getAccountById(userIdArg);
      if (!account || account.status !== 'active') {
        return null;
      }
      return this.accountToUser(account);
    }

    return this.users.get(userIdArg) || null;
  }

  private async getPersistentAccountState(): Promise<{
    dbEnabled: boolean;
    dbReady: boolean;
    store: plugins.idpSdkServer.SmartdataAccountStore | null;
    hasPersistentAdmin: boolean;
  }> {
    const dbEnabled = this.isPersistenceEnabled();
    const store = dbEnabled ? this.getAccountStore() : null;
    const dbReady = !!store;
    const hasPersistentAdmin = store ? await store.hasActiveAdminAccount() : false;
    return { dbEnabled, dbReady, store, hasPersistentAdmin };
  }

  private isPersistenceEnabled(): boolean {
    return this.opsServerRef.dcRouterRef.options.dbConfig?.enabled !== false;
  }

  private getAccountStore(): plugins.idpSdkServer.SmartdataAccountStore | null {
    if (!this.isPersistenceEnabled()) {
      return null;
    }
    const dcRouterDb = this.opsServerRef.dcRouterRef.dcRouterDb;
    if (!dcRouterDb?.isReady()) {
      return null;
    }
    if (!this.accountStore) {
      this.accountStore = new plugins.idpSdkServer.SmartdataAccountStore({
        smartdataDb: dcRouterDb.getDb(),
      });
    }
    return this.accountStore;
  }

  private getIdpClient(): Pick<plugins.idpSdkServer.IdpGlobalServerClient, 'loginWithEmailAndPassword' | 'stop'> | undefined {
    const configuredClient = this.opsServerRef.dcRouterRef.options.adminAuth?.idpClient;
    if (configuredClient) {
      return configuredClient;
    }

    const baseUrl = this.opsServerRef.dcRouterRef.options.adminAuth?.idpGlobalUrl || process.env.DCROUTER_IDP_GLOBAL_URL;
    if (!this.idpClient) {
      this.idpClient = baseUrl
        ? new plugins.idpSdkServer.IdpGlobalServerClient({ baseUrl })
        : new plugins.idpSdkServer.IdpGlobalServerClient({} as plugins.idpSdkServer.IIdpGlobalServerClientOptions);
      this.ownsIdpClient = true;
    }
    return this.idpClient;
  }

  private isIdpGlobalConfigured(): boolean {
    return true;
  }

  private accountToUser(accountArg: plugins.idpSdkServer.IIdpSdkAccount): TAdminUser {
    return {
      id: accountArg.id,
      username: accountArg.email,
      email: accountArg.email,
      name: accountArg.name,
      role: accountArg.role,
      status: accountArg.status,
      authSources: accountArg.authSources,
    };
  }

  private async createIdentityForUser(userArg: TAdminUser): Promise<interfaces.data.IIdentity> {
    const expiresAtTimestamp = Date.now() + 3600 * 1000 * 24; // 24 hours
    const jwt = await this.smartjwtInstance.createJWT({
      userId: userArg.id,
      status: 'loggedIn',
      expiresAt: expiresAtTimestamp,
    });

    return {
      jwt,
      userId: userArg.id,
      name: userArg.name || userArg.username,
      expiresAt: expiresAtTimestamp,
      role: userArg.role,
      type: 'user',
    };
  }
}
