import * as interfaces from '../ts_interfaces/index.js';
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';

export class ApiToken {
  private clientRef: DcRouterApiClient;

  // Data from IApiTokenInfo
  public id: string;
  public name: string;
  public scopes: interfaces.data.TApiTokenScope[];
  public createdAt: number;
  public expiresAt: number | null;
  public lastUsedAt: number | null;
  public enabled: boolean;

  /** Only set on creation or roll. Not persisted on server side. */
  public tokenValue?: string;

  constructor(clientRef: DcRouterApiClient, data: interfaces.data.IApiTokenInfo, tokenValue?: string) {
    this.clientRef = clientRef;
    this.id = data.id;
    this.name = data.name;
    this.scopes = data.scopes;
    this.createdAt = data.createdAt;
    this.expiresAt = data.expiresAt;
    this.lastUsedAt = data.lastUsedAt;
    this.enabled = data.enabled;
    this.tokenValue = tokenValue;
  }

  public async revoke(): Promise<void> {
    const response = await this.clientRef.request<interfaces.requests.IReq_RevokeApiToken>(
      'revokeApiToken',
      this.clientRef.buildRequestPayload({ id: this.id }) as any,
    );
    if (!response.success) {
      throw new Error(response.message || 'Failed to revoke token');
    }
  }

  public async roll(): Promise<string> {
    const response = await this.clientRef.request<interfaces.requests.IReq_RollApiToken>(
      'rollApiToken',
      this.clientRef.buildRequestPayload({ id: this.id }) as any,
    );
    if (!response.success) {
      throw new Error(response.message || 'Failed to roll token');
    }
    this.tokenValue = response.tokenValue;
    return response.tokenValue!;
  }

  public async toggle(enabled: boolean): Promise<void> {
    const response = await this.clientRef.request<interfaces.requests.IReq_ToggleApiToken>(
      'toggleApiToken',
      this.clientRef.buildRequestPayload({ id: this.id, enabled }) as any,
    );
    if (!response.success) {
      throw new Error(response.message || 'Failed to toggle token');
    }
    this.enabled = enabled;
  }
}

export class ApiTokenBuilder {
  private clientRef: DcRouterApiClient;
  private tokenName: string = '';
  private tokenScopes: interfaces.data.TApiTokenScope[] = [];
  private tokenExpiresInDays?: number | null;

  constructor(clientRef: DcRouterApiClient) {
    this.clientRef = clientRef;
  }

  public setName(name: string): this {
    this.tokenName = name;
    return this;
  }

  public setScopes(scopes: interfaces.data.TApiTokenScope[]): this {
    this.tokenScopes = scopes;
    return this;
  }

  public addScope(scope: interfaces.data.TApiTokenScope): this {
    if (!this.tokenScopes.includes(scope)) {
      this.tokenScopes.push(scope);
    }
    return this;
  }

  public setExpiresInDays(days: number | null): this {
    this.tokenExpiresInDays = days;
    return this;
  }

  public async save(): Promise<ApiToken> {
    const response = await this.clientRef.request<interfaces.requests.IReq_CreateApiToken>(
      'createApiToken',
      this.clientRef.buildRequestPayload({
        name: this.tokenName,
        scopes: this.tokenScopes,
        expiresInDays: this.tokenExpiresInDays,
      }) as any,
    );
    if (!response.success) {
      throw new Error(response.message || 'Failed to create API token');
    }
    return new ApiToken(
      this.clientRef,
      {
        id: response.tokenId!,
        name: this.tokenName,
        scopes: this.tokenScopes,
        createdAt: Date.now(),
        expiresAt: this.tokenExpiresInDays
          ? Date.now() + this.tokenExpiresInDays * 24 * 60 * 60 * 1000
          : null,
        lastUsedAt: null,
        enabled: true,
      },
      response.tokenValue,
    );
  }
}

export class ApiTokenManager {
  private clientRef: DcRouterApiClient;

  constructor(clientRef: DcRouterApiClient) {
    this.clientRef = clientRef;
  }

  public async list(): Promise<ApiToken[]> {
    const response = await this.clientRef.request<interfaces.requests.IReq_ListApiTokens>(
      'listApiTokens',
      this.clientRef.buildRequestPayload() as any,
    );
    return response.tokens.map((t) => new ApiToken(this.clientRef, t));
  }

  public async create(options: {
    name: string;
    scopes: interfaces.data.TApiTokenScope[];
    expiresInDays?: number | null;
  }): Promise<ApiToken> {
    return this.build()
      .setName(options.name)
      .setScopes(options.scopes)
      .setExpiresInDays(options.expiresInDays ?? null)
      .save();
  }

  public build(): ApiTokenBuilder {
    return new ApiTokenBuilder(this.clientRef);
  }
}
