import {
  type ResponseGenerator,
  setupWorker,
  type MockHandler,
  type SetupWorkerOptions,
  type InterceptOptions,
  type SerializedRequest,
} from '@jarrodek/amw'
import type { IOrganization } from '../models/store/Organization.js'
import type { GroupSchema } from '../models/store/Group.js'
import type { IUser } from '../models/store/User.js'
import type { InvitationSchema } from '../models/store/Invitation.js'
import { type IFile, type FileBreadcrumb } from '../models/store/File.js'
import type { ContextChangeRecord, ContextListResult, IBulkOperationResult } from '../events/BaseEvents.js'
import type { TrashEntry } from '../models/TrashEntry.js'
import { RouteBuilder } from './RouteBuilder.js'
import { ModelingMock } from '../mocking/ModelingMock.js'
import type {
  DataCatalogSchema,
  DataCatalogSchemaWithVersion,
  DataCatalogStatus,
  DataCatalogVersionInfo,
} from '../models/DataCatalog.js'
import type { DataCatalogVersionSchema } from '../models/DataCatalogVersion.js'
import { DataDomain, type DataDomainSchema } from '../modeling/DataDomain.js'
import type { ForeignDomainDependency } from '../modeling/types.js'
import { nanoid } from '../nanoid.js'

export interface MockResult {
  /**
   * Custom response generator for the mock intercept.
   */
  response?: ResponseGenerator
  /**
   * If true, the request body will be generated using the body generator.
   * It is useful when only setting up response headers or status code, but
   * the request body should still be generated, even when missing in the `response` option.
   */
  forceBody?: boolean
}

export interface MockListResult extends MockResult {
  /**
   * Number of items to generate in the list response.
   */
  size?: number
  /**
   * If true, the response will include a cursor for pagination.
   * If false, the response will not include a cursor.
   * If not set, a random choice will be made.
   */
  cursor?: boolean
}

/**
 * SDK mocking utility for testing. Uses Service Workers to intercept HTTP requests
 * and provide mock responses for API calls.
 *
 * This class uses the `@jarrodek/amw` library to set up a Service Worker that intercepts
 * fetch requests matching the configured routes. Each method adds an intercept for a specific
 * API endpoint, returning either random generated data or custom responses.
 *
 * @example
 * ```typescript
 * import { SdkMock } from '@api-client/core/sdk/SdkMock.js';
 * import { StoreSdk } from '@api-client/core/sdk/StoreSdkWeb.js';
 *
 * const sdk = new StoreSdk('http://localhost:8080');
 * const mocker = new SdkMock({
 *   swPath: '/mockServiceWorker.js', // Path to the Service Worker script
 *   base: 'http://localhost:8080'    // Base URL matching the SDK
 * });
 *
 * // Initialize the Service Worker
 * await mocker.setup();
 *
 * // Add intercept - returns random organizations
 * await mocker.organizations.list();
 *
 * // Custom response with specific data
 * await mocker.organizations.create({
 *   response: {
 *     status: 201,
 *     headers: { 'Content-Type': 'application/json', 'X-Custom-Header': 'value' },
 *     body: JSON.stringify({ key: 'custom-id', name: 'Custom Org' })
 *   }
 * });
 *
 * // Control pagination in list responses
 * await mocker.groups.list({
 *   size: 10,        // Number of items to generate
 *   cursor: true     // Include pagination cursor
 * });
 *
 * // Error simulation
 * await mocker.users.me({
 *   response: {
 *     status: 404,
 *     headers: { 'Content-Type': 'application/json' },
 *     body: JSON.stringify({ error: 'Not found' })
 *   }
 * });
 *
 * // Remove all intercepts (keep Service Worker active)
 * await mocker.reset();
 *
 * // Stop and remove the Service Worker
 * await mocker.teardown();
 * ```
 *
 * @TODO: Add a in-memory state store so that mocks can maintain state across requests.
 * This way, operations like create, update, delete can affect subsequent list/read calls
 * and we can mimic the API behavior more closely.
 */
export class SdkMock {
  handler?: MockHandler
  gen = new ModelingMock()

  constructor(public options?: SetupWorkerOptions) {}

  /**
   * Initializes the mock handler. It uses options provided in the constructor.
   * It has to be called before using any of the mock methods.
   */
  async setup(): Promise<void> {
    this.handler = await setupWorker(this.options)
  }

  /**
   * Removes the mock worker and all intercepts.
   */
  async teardown(): Promise<void> {
    await this.mock.stop()
  }

  /**
   * Removes all added intercepts.
   */
  async reset(): Promise<void> {
    await this.mock.reset()
  }

  get mock(): MockHandler {
    if (!this.handler) {
      throw new Error('Mock handler not initialized. Call setup() first.')
    }
    return this.handler
  }

  protected createCursorOption(init: MockListResult = {}): string | undefined {
    if (init.cursor === false) {
      return undefined
    }
    const hasCursor = init.cursor === true ? true : this.gen.faker.datatype.boolean()
    if (!hasCursor) {
      return undefined
    }
    return this.gen.faker.internet.jwt()
  }

  protected createDefaultResponse(
    status: number,
    headers?: Record<string, string>,
    body?: (req: SerializedRequest) => string,
    userConfig?: MockResult
  ): ResponseGenerator {
    let respond: ResponseGenerator
    if (userConfig?.response) {
      // user config takes precedence
      respond = userConfig.response
    } else {
      respond = {}
    }
    // Set defaults if not provided in user config
    if (!respond.status) {
      respond.status = status
    }
    // only set headers if the user didn't configure the response only.
    // The user may want to remove default headers.
    if (!userConfig || !userConfig.response) {
      respond.headers = headers
    }
    if (!respond.body && userConfig?.forceBody && body) {
      // when body is missing and forceBody is set, generate the body
      respond.body = (req: SerializedRequest) => body(req)
    } else if (body && (!userConfig || !userConfig.response)) {
      // we set the body by default when the user config is missing
      respond.body = (req: SerializedRequest) => body(req)
    }
    return respond
  }

  /**
   * Organization API mocks.
   */
  organizations = {
    /**
     * Adds an intercept to mock the `organizations.list()` method.
     * @param options Optional response configuration
     */
    list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.organization.organizations(init?.size ?? 5),
      //     cursor: this.createCursorOption(init),
      //   } as ContextListResult<IOrganization>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () =>
          JSON.stringify({
            items: this.gen.organization.organizations(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          } as ContextListResult<IOrganization>),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.organizations(),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Adds an intercept to mock the `organizations.create()` method.
     * @param options Optional response configuration
     */
    create: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.organization.organization()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.organization.organization()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.organizations(),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    invitations: {
      list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify({
        //     items: this.gen.invitation.invitations(init?.size ?? 5),
        //     cursor: this.createCursorOption(init),
        //   } as ContextListResult<InvitationSchema>),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => {
            const obj: ContextListResult<InvitationSchema> = {
              items: this.gen.invitation.invitations(init?.size ?? 5),
              cursor: this.createCursorOption(init),
            }
            return JSON.stringify(obj)
          },
          init
        )
        await mock.add(
          {
            match: {
              uri: RouteBuilder.invitations(':oid'),
              methods: ['GET'],
            },
            respond,
          },
          options
        )
      },
      create: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.invitation.invitation()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.invitation.invitation()),
          init
        )
        await mock.add(
          {
            match: {
              uri: RouteBuilder.invitations(':oid'),
              methods: ['POST'],
            },
            respond,
          },
          options
        )
      },
      findByToken: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.invitation.invitation()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.invitation.invitation()),
          init
        )

        await mock.add(
          {
            match: {
              uri: RouteBuilder.findInvitation(),
              methods: ['GET'],
            },
            respond,
          },
          options
        )
      },
      decline: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.invitation.invitation()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.invitation.invitation()),
          init
        )

        await mock.add(
          {
            match: {
              uri: RouteBuilder.declineInvitation(':oid', ':id'),
              methods: ['POST'],
            },
            respond,
          },
          options
        )
      },
      delete: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.invitation.invitation()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.invitation.invitation()),
          init
        )

        await mock.add(
          {
            match: {
              uri: RouteBuilder.invitation(':oid', ':id'),
              methods: ['DELETE'],
            },
            respond,
          },
          options
        )
      },
      patch: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.invitation.invitation()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.invitation.invitation()),
          init
        )
        await mock.add(
          {
            match: {
              uri: RouteBuilder.invitation(':oid', ':id'),
              methods: ['PATCH'],
            },
            respond,
          },
          options
        )
      },
      resend: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.invitation.invitation()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.invitation.invitation()),
          init
        )

        await mock.add(
          {
            match: {
              uri: RouteBuilder.resendInvitation(':oid', ':id'),
              methods: ['PUT'],
            },
            respond,
          },
          options
        )
      },
    },

    users: {
      list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify({
        //     items: this.gen.users.users(init?.size ?? 5),
        //     cursor: this.createCursorOption(init),
        //   } as ContextListResult<IUser>),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => {
            const obj: ContextListResult<IUser> = {
              items: this.gen.users.users(init?.size ?? 5),
              cursor: this.createCursorOption(init),
            }
            return JSON.stringify(obj)
          },
          init
        )
        await mock.add(
          {
            match: {
              uri: RouteBuilder.organizationUsers(':oid'),
              methods: ['GET'],
            },
            respond,
          },
          options
        )
      },
      read: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.users.user()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.users.user()),
          init
        )
        await mock.add(
          {
            match: {
              uri: RouteBuilder.organizationUser(':oid', ':id'),
              methods: ['GET'],
            },
            respond,
          },
          options
        )
      },
      readBatch: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        const path = RouteBuilder.organizationUserBatch(':oid')
        const respond = init?.response ?? {
          status: 200,
          headers: { 'content-type': 'application/json' },
          body: JSON.stringify({
            items: this.gen.users.users(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          } as ContextListResult<IUser>),
        }
        await mock.add(
          {
            match: {
              uri: path,
              methods: ['POST'],
            },
            respond,
          },
          options
        )
      },
      activate: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.users.user()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.users.user()),
          init
        )
        await mock.add(
          {
            match: {
              uri: RouteBuilder.organizationUserActivate(':oid', ':id'),
              methods: ['POST'],
            },
            respond,
          },
          options
        )
      },
      deactivate: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 200,
        //   headers: { 'content-type': 'application/json' },
        //   body: JSON.stringify(this.gen.users.user()),
        // }
        const respond = this.createDefaultResponse(
          200,
          { 'content-type': 'application/json' },
          () => JSON.stringify(this.gen.users.user()),
          init
        )
        await mock.add(
          {
            match: {
              uri: RouteBuilder.organizationUserDeactivate(':oid', ':id'),
              methods: ['POST'],
            },
            respond,
          },
          options
        )
      },
      delete: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
        const { mock } = this
        // const respond = init?.response ?? {
        //   status: 204,
        // }
        const respond = this.createDefaultResponse(204, undefined, undefined, init)
        await mock.add(
          {
            match: {
              uri: RouteBuilder.organizationUser(':oid', ':id'),
              methods: ['DELETE'],
            },
            respond,
          },
          options
        )
      },
    },
  }

  /**
   * Group API mocks.
   */
  groups = {
    /**
     * Mocks the `groups.list()` method.
     * @param options Optional response customization.
     */
    list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.group.groups(init?.size ?? 5),
      //     cursor: this.createCursorOption(init),
      //   } as ContextListResult<GroupSchema>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<GroupSchema> = {
            items: this.gen.group.groups(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.groups(':oid'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `groups.create()` method.
     * @param options Optional response customization.
     */
    create: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 201,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.group.group()),
      // }
      const respond = this.createDefaultResponse(
        201,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.group.group()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.groups(':oid'),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `groups.update()` method.
     * @param options Optional response customization.
     */
    update: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.group.group()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.group.group()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.group(':oid', ':key'),
            methods: ['PATCH'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `groups.delete()` method.
     * @param options Optional response customization.
     */
    delete: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 204,
      // }
      const respond = this.createDefaultResponse(204, undefined, undefined, init)
      await mock.add(
        {
          match: {
            uri: RouteBuilder.group(':oid', ':key'),
            methods: ['DELETE'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `groups.addUsers()` method.
     */
    addUsers: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.group.group()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.group.group()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.groupUsers(':oid', ':key'),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `groups.removeUsers()` method.
     */
    removeUsers: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.group.group()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.group.group()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.groupUsers(':oid', ':key'),
            methods: ['DELETE'],
          },
          respond,
        },
        options
      )
    },
  }

  /**
   * User API mocks.
   */
  users = {
    /**
     * Mocks the `user.me()` method.
     * @param options Optional response customization.
     */
    me: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.users.user()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.users.user()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.usersMe(),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },
  }

  /**
   * Files API mocks.
   */
  files = {
    /**
     * Mocks the `file.list()` method.
     */
    list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.files.files(init?.size ?? 5),
      //     cursor: this.createCursorOption(init),
      //   } as ContextListResult<IFile>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<IFile> = {
            items: this.gen.files.files(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.files(':oid'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.createMeta()` method.
     */
    createMeta: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 201,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.files.file()),
      // }
      const respond = this.createDefaultResponse(
        201,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.files.file()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.files(':oid'),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.createMedia()` method.
     */
    createMedia: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      // }
      const respond = this.createDefaultResponse(200, undefined, undefined, init)
      await mock.add(
        {
          match: {
            uri: RouteBuilder.fileMedia(':oid', ':id'),
            methods: ['PUT'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.create()` method.
     */
    create: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      await this.files.createMeta(init, options)
      // When SDK's file.create() is called, it responds with
      // what the result of file.createMeta() would be.
      // Because of that, we don't need to configure the media request.
      await this.files.createMedia(undefined, options)
    },

    /**
     * Mocks the `file.createFolder()` method.
     */
    createFolder: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 201,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.files.folder()),
      // }
      const respond = this.createDefaultResponse(
        201,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.files.folder()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.files(':oid'),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.read()` method.
     */
    read: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.files.file()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.files.file()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.file(':oid', ':id'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.readMedia()` method.
     */
    readMedia: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: {
      //     'content-type': 'application/json',
      //     'x-version': `${this.gen.faker.number.int({ min: 1, max: 100 })}`,
      //   },
      //   body: JSON.stringify({ data: this.gen.faker.lorem.sentences() }),
      // }
      const respond = this.createDefaultResponse(
        200,
        {
          'content-type': 'application/json',
          'x-version': `${this.gen.faker.number.int({ min: 1, max: 100 })}`,
        },
        () => JSON.stringify({ data: this.gen.faker.lorem.sentences() }),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.fileMedia(':oid', ':id'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.readBulk()` method.
     */
    readBulk: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.files.files(init?.size ?? 5),
      //   } as IBulkOperationResult<IFile>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: IBulkOperationResult<IFile> = {
            items: this.gen.files.files(init?.size ?? 5),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.filesBatch(':oid'),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.patch()` method.
     */
    patch: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.files.file()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.files.file()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.file(':oid', ':id'),
            methods: ['PATCH'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.patchMedia()` method.
     */
    patchMedia: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.patch.mediaPatchRevision()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.patch.mediaPatchRevision()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.fileMedia(':oid', ':id'),
            methods: ['PATCH'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.delete()` method.
     */
    delete: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 204,
      // }
      const respond = this.createDefaultResponse(204, undefined, undefined, init)
      await mock.add(
        {
          match: {
            uri: RouteBuilder.file(':oid', ':id'),
            methods: ['DELETE'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.deleteBulk()` method.
     */
    deleteBulk: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 204,
      // }
      const respond = this.createDefaultResponse(204, undefined, undefined, init)
      await mock.add(
        {
          match: {
            uri: RouteBuilder.files(':oid'),
            methods: ['DELETE'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.patchUsers()` method.
     */
    patchUsers: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify(this.gen.files.file()),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => JSON.stringify(this.gen.files.file()),
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.filesAccess(':oid', ':id'),
            methods: ['PATCH'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.addUser()` method.
     */
    addUser: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      await this.files.patchUsers(init, options)
    },

    /**
     * Mocks the `file.removeUser()` method.
     */
    removeUser: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      await this.files.patchUsers(init, options)
    },

    /**
     * Mocks the `file.listUsers()` method.
     */
    listUsers: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.users.users(init?.size ?? 5),
      //     cursor: this.createCursorOption(init),
      //   } as ContextListResult<IUser>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<IUser> = {
            items: this.gen.users.users(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.fileUsers(':oid', ':id'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    /**
     * Mocks the `file.breadcrumbs()` method.
     */
    breadcrumbs: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.files.fileBreadcrumbs(init?.size ?? 5),
      //     cursor: this.createCursorOption(init),
      //   } as ContextListResult<FileBreadcrumb>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<FileBreadcrumb> = {
            items: this.gen.files.fileBreadcrumbs(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.fileBreadcrumbs(':oid', ':id'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },
  }

  /**
   * @deprecated Use `files` instead.
   */
  file = this.files

  /**
   * Shared API mocks.
   */
  shared = {
    list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.files.files(init?.size ?? 5),
      //     cursor: this.createCursorOption(init),
      //   } as ContextListResult<IFile>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<IFile> = {
            items: this.gen.files.files(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.shared(':oid'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },
  }

  /**
   * Trash API mocks.
   */
  trash = {
    list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 200,
      //   headers: { 'content-type': 'application/json' },
      //   body: JSON.stringify({
      //     items: this.gen.trash.trashEntries(init?.size ?? 5),
      //     cursor: this.createCursorOption(init),
      //   } as ContextListResult<TrashEntry>),
      // }
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<TrashEntry> = {
            items: this.gen.trash.trashEntries(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.trash(':oid'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },
    delete: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 204,
      // }
      const respond = this.createDefaultResponse(204, undefined, undefined, init)
      await mock.add(
        {
          match: {
            uri: RouteBuilder.trashBatchDelete(':oid'),
            methods: ['DELETE'],
          },
          respond,
        },
        options
      )
    },
    restore: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 204,
      // }
      const respond = this.createDefaultResponse(204, undefined, undefined, init)
      await mock.add(
        {
          match: {
            uri: RouteBuilder.trashBatchRestore(':oid'),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },
    empty: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      // const respond = init?.response ?? {
      //   status: 204,
      // }
      const respond = this.createDefaultResponse(204, undefined, undefined, init)
      await mock.add(
        {
          match: {
            uri: RouteBuilder.trashEmpty(':oid'),
            methods: ['DELETE'],
          },
          respond,
        },
        options
      )
    },
  }

  /**
   * Trash Data Catalog mocks.
   */
  dataCatalog = {
    list: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<DataCatalogSchemaWithVersion> = {
            items: this.gen.dataCatalog.dataCatalogsWithVersion(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalog(),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    listVersions: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj: ContextListResult<DataCatalogVersionInfo> = {
            items: this.gen.dataCatalog.versionInfos(init?.size ?? 5),
            cursor: this.createCursorOption(init),
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogEntryVersions(':id'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    publish: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj = this.gen.dataCatalog.dataCatalog()
          const result: ContextChangeRecord<DataCatalogSchema> = {
            key: obj.key,
            item: obj,
            kind: obj.kind,
          }
          return JSON.stringify(result)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalog(),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    read: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const obj = this.gen.dataCatalog.dataCatalog({ key: req.params.id })
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogEntry(':id'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    deprecate: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const obj = this.gen.dataCatalog.dataCatalog({ key: req.params.id })
          const result: ContextChangeRecord<DataCatalogSchema> = {
            key: obj.key,
            item: obj,
            kind: obj.kind,
          }
          return JSON.stringify(result)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogDeprecate(':id'),
            methods: ['PUT'],
          },
          respond,
        },
        options
      )
    },

    unpublish: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const obj = this.gen.dataCatalog.dataCatalog({ key: req.params.id })
          const result: ContextChangeRecord<DataCatalogSchema> = {
            key: obj.key,
            item: obj,
            kind: obj.kind,
          }
          return JSON.stringify(result)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogUnpublish(':id'),
            methods: ['PUT'],
          },
          respond,
        },
        options
      )
    },

    publishVersion: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const raw = req.body as string
          const body = JSON.parse(raw) as DataCatalogVersionSchema

          const result: ContextChangeRecord<DataCatalogVersionSchema> = {
            key: body.key,
            item: body,
            kind: body.kind,
          }
          return JSON.stringify(result)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogEntryVersions(':id'),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    readVersion: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const obj = this.gen.dataCatalog.dataCatalogVersion({ key: req.params.vid, catalogKey: req.params.id })
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogVersion(':id', ':vid'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },

    listDependencies: async (init?: MockListResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const requirements = JSON.parse(req.body as string) as { items: ForeignDomainDependency[] }
          const items: DataDomainSchema[] = []
          for (const item of requirements.items) {
            const domain = new DataDomain({
              key: item.key,
              info: { name: `Domain ${item.key}`, version: item.version },
            })
            items.push(domain.toJSON())
          }
          const obj: ContextListResult<DataDomainSchema> = {
            items: items,
          }
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogDependencies(),
            methods: ['POST'],
          },
          respond,
        },
        options
      )
    },

    deprecateVersion: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const obj = this.gen.dataCatalog.dataCatalogVersion({ key: req.params.vid, catalogKey: req.params.id })
          obj.deprecated = true
          obj.deprecatedAt = this.gen.faker.date.past().getTime()
          obj.deprecatedBy = nanoid()
          const info = JSON.parse(req.body as string) as { reason: string }
          obj.deprecationReason = info.reason
          const result: ContextChangeRecord<DataCatalogVersionSchema> = {
            key: obj.key,
            item: obj,
            kind: obj.kind,
          }
          return JSON.stringify(result)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogVersionDeprecate(':id', ':vid'),
            methods: ['PUT'],
          },
          respond,
        },
        options
      )
    },

    unpublishVersion: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        (req: SerializedRequest) => {
          const obj = this.gen.dataCatalog.dataCatalogVersion({ key: req.params.vid, catalogKey: req.params.id })
          obj.unpublishedAt = this.gen.faker.date.past().getTime()
          obj.scope = 'private'
          const result: ContextChangeRecord<DataCatalogVersionSchema> = {
            key: obj.key,
            item: obj,
            kind: obj.kind,
          }
          return JSON.stringify(result)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogVersionUnpublish(':id', ':vid'),
            methods: ['PUT'],
          },
          respond,
        },
        options
      )
    },

    checkPublicationStatus: async (init?: MockResult, options?: InterceptOptions): Promise<void> => {
      const { mock } = this
      const respond = this.createDefaultResponse(
        200,
        { 'content-type': 'application/json' },
        () => {
          const obj = this.gen.dataCatalog.dataCatalog() as DataCatalogStatus
          obj.versions = this.gen.dataCatalog.versionInfos(1)
          return JSON.stringify(obj)
        },
        init
      )
      await mock.add(
        {
          match: {
            uri: RouteBuilder.dataCatalogStatus(':id'),
            methods: ['GET'],
          },
          respond,
        },
        options
      )
    },
  }
}
