import { TestContext } from '@japa/runner/core'

/**
 * Common test utilities for the API Client Core
 */
export const TestUtils = {
  /**
   * Creates a mock HTTP response
   */
  createMockResponse(status = 200, body: unknown = {}, headers: Record<string, string> = {}): Response {
    return new Response(JSON.stringify(body), {
      status,
      headers: {
        'Content-Type': 'application/json',
        ...headers,
      },
    })
  },

  /**
   * Creates a test URL with optional query parameters
   */
  createTestUrl(path = '/', params: Record<string, string> = {}): URL {
    const url = new URL(path, 'https://api.example.com')
    Object.entries(params).forEach(([key, value]) => {
      url.searchParams.set(key, value)
    })
    return url
  },

  /**
   * Waits for a specified amount of time
   */
  async sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms))
  },

  /**
   * Creates a test request object
   */
  createTestRequest(
    method = 'GET',
    url: string | URL = 'https://api.example.com',
    body?: BodyInit,
    headers: Record<string, string> = {}
  ): Request {
    return new Request(url, {
      method,
      body,
      headers: {
        'Content-Type': 'application/json',
        ...headers,
      },
    })
  },

  /**
   * Generates random test data
   */
  generateTestData() {
    return {
      id: Math.random().toString(36).substr(2, 9),
      timestamp: Date.now(),
      randomString: Math.random().toString(36).substr(2, 10),
      randomNumber: Math.floor(Math.random() * 1000),
      randomBoolean: Math.random() > 0.5,
    }
  },

  /**
   * Creates a mock authorization configuration
   */
  createMockAuthConfig() {
    return {
      clientId: 'test-client-id',
      clientSecret: 'test-client-secret',
      authorizationUri: 'https://auth.example.com/oauth/authorize',
      accessTokenUri: 'https://auth.example.com/oauth/token',
      scopes: ['read', 'write'],
    }
  },

  /**
   * Creates a mock API project structure
   */
  createMockProject() {
    return {
      kind: 'Project',
      key: 'test-project',
      info: {
        name: 'Test Project',
        version: '1.0.0',
        description: 'A test API project',
      },
      definitions: {},
      environments: [],
      requests: [],
      folders: [],
    }
  },

  /**
   * Asserts that an error is thrown with specific message
   */
  async assertThrows(fn: () => Promise<unknown> | unknown, expectedMessage?: string | RegExp): Promise<Error> {
    try {
      await fn()
      throw new Error('Expected function to throw an error')
    } catch (error) {
      const typedError = error as Error
      if (expectedMessage) {
        if (typeof expectedMessage === 'string') {
          if (!typedError.message.includes(expectedMessage)) {
            throw new Error(`Expected error message to include "${expectedMessage}", got "${typedError.message}"`)
          }
        } else if (expectedMessage instanceof RegExp) {
          if (!expectedMessage.test(typedError.message)) {
            throw new Error(`Expected error message to match ${expectedMessage}, got "${typedError.message}"`)
          }
        }
      }
      return typedError
    }
  },

  /**
   * Validates that an object has required properties
   */
  validateRequiredProperties<T extends Record<string, unknown>>(obj: T, requiredProps: (keyof T)[]): void {
    for (const prop of requiredProps) {
      if (!(prop in obj)) {
        throw new Error(`Missing required property: ${String(prop)}`)
      }
      if (obj[prop] === undefined || obj[prop] === null) {
        throw new Error(`Property ${String(prop)} cannot be null or undefined`)
      }
    }
  },

  /**
   * Creates a test environment configuration
   */
  createTestEnvironment() {
    return {
      variables: [
        { name: 'baseUrl', value: 'https://api.example.com' },
        { name: 'apiKey', value: 'test-api-key' },
        { name: 'version', value: 'v1' },
      ],
      oauth2: {
        port: 8080,
        issuer: 'https://oauth.example.com',
      },
    }
  },

  /**
   * Mocks the fetch function with a custom response
   */
  mockFetch(response: Response | ((input: RequestInfo | URL, init?: RequestInit) => Response)): () => void {
    const originalFetch = globalThis.fetch

    globalThis.fetch =
      typeof response === 'function'
        ? (input: RequestInfo | URL, init?: RequestInit) =>
            Promise.resolve((response as (input: RequestInfo | URL, init?: RequestInit) => Response)(input, init))
        : () => Promise.resolve(response)

    // Return cleanup function
    return () => {
      globalThis.fetch = originalFetch
    }
  },
}

/**
 * Test decorators and helpers for Japa tests
 */
export const JapaTestHelpers = {
  /**
   * Timeout decorator for tests that may take longer
   */
  timeout(ms: number) {
    return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
      const originalMethod = descriptor.value
      descriptor.value = function (...args: unknown[]) {
        const [context] = args
        // Set timeout on test context (implementation may vary)
        if (context && typeof context === 'object' && 'timeout' in context) {
          ;(context as { timeout: number }).timeout = ms
        }
        return originalMethod.apply(this, args)
      }
    }
  },

  /**
   * Skip test conditionally
   */
  skipIf(condition: boolean, reason?: string) {
    return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
      if (condition) {
        descriptor.value = function (context: TestContext) {
          context.test.skip(true, reason || 'Skipped conditionally')
        }
      }
    }
  },

  /**
   * Run test only in specific environment
   */
  onlyInEnvironment(env: 'node' | 'browser') {
    return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
      // eslint-disable-next-line no-restricted-globals
      const isNode = typeof window === 'undefined'
      const shouldRun = (env === 'node' && isNode) || (env === 'browser' && !isNode)

      if (!shouldRun) {
        descriptor.value = function (context: TestContext) {
          context.test.skip(true, `Only runs in ${env} environment`)
        }
      }
    }
  },
}

/**
 * Browser-specific test utilities
 */
export const BrowserTestUtils = {
  /**
   * Simulates a browser event
   */
  dispatchEvent(element: Element, eventType: string, options: EventInit = {}): Event {
    const event = new Event(eventType, { bubbles: true, cancelable: true, ...options })
    element.dispatchEvent(event)
    return event
  },

  /**
   * Creates a mock localStorage implementation
   */
  createMockLocalStorage(): Storage {
    const storage: Record<string, string> = {}

    return {
      getItem: (key: string) => storage[key] || null,
      setItem: (key: string, value: string) => {
        storage[key] = value
      },
      removeItem: (key: string) => {
        // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
        delete storage[key]
      },
      clear: () => {
        Object.keys(storage).forEach((key) => {
          // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
          delete storage[key]
        })
      },
      key: (index: number) => Object.keys(storage)[index] || null,
      get length() {
        return Object.keys(storage).length
      },
    }
  },

  /**
   * Waits for DOM element to appear
   */
  async waitForElement(selector: string, timeout = 5000): Promise<Element> {
    const startTime = Date.now()

    while (Date.now() - startTime < timeout) {
      // eslint-disable-next-line no-restricted-globals
      const element = document.querySelector(selector)
      if (element) {
        return element
      }
      await TestUtils.sleep(50)
    }

    throw new Error(`Element with selector "${selector}" not found within ${timeout}ms`)
  },

  /**
   * Creates a mock file for file upload testing
   */
  createMockFile(name = 'test.txt', content = 'test content', type = 'text/plain'): File {
    return new File([content], name, { type })
  },
}
