import { test } from '@japa/runner'
import {
  IResponse,
  Response,
  IHttpRequest,
  RequestTime,
  CoreEngine,
  HttpEngineOptions,
  Headers,
  PayloadSerializer,
  RequestAuthorizationKind,
  createLogger,
} from '../../../../src/index.js'
import { HttpEngineErrorFactory } from '../../../../src/runtime/http-engine/errors/index.js'
import { MessageBuilder } from '../../../../src/runtime/http-engine/message/index.js'
import { AuthManager } from '../../../../src/runtime/http-engine/auth/index.js'
import { ConnectionManager } from '../../../../src/runtime/http-engine/connections/index.js'

const logger = createLogger()

test.group('CoreEngine - Message Builder Integration', () => {
  test('builds HTTP messages correctly with MessageBuilder', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/api/endpoint?query=param`,
      method: 'POST',
      headers: 'content-type: text/plain',
      payload: PayloadSerializer.stringifyBuffer(Buffer.from('test data')),
    }
    const opts: HttpEngineOptions = {
      timeout: 50000,
      followRedirects: false,
      logger,
    }
    const engine = new CoreEngine(request, opts)
    const message = await engine.prepareMessage()

    assert.isTrue(message instanceof Buffer)
    const messageStr = message.toString()
    assert.include(messageStr, 'POST /api/endpoint?query=param HTTP/1.1')
    assert.include(messageStr, 'Host: localhost:')
    assert.include(messageStr, 'content-type: text/plain')
  }).tags(['@core-engine2', '@message-builder'])

  test('handles proxy requests with correct status line', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/api/endpoint`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      proxy: `http://localhost:${httpConfig.httpPort}`,
    }
    const engine = new CoreEngine(request, opts)
    const message = await engine.prepareMessage()

    const messageStr = message.toString()
    // For proxy requests, the full URL should be in the status line
    assert.include(messageStr, `GET http://localhost:${httpConfig.httpPort}/api/endpoint HTTP/1.1`)
  }).tags(['@core-engine2', '@message-builder', '@proxy'])

  test('handles HTTPS tunnel requests correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `https://localhost:${httpConfig.httpsPort}/api/endpoint`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      proxy: `http://localhost:${httpConfig.httpPort}`,
    }
    const engine = new CoreEngine(request, opts)
    const message = await engine.prepareMessage()

    const messageStr = message.toString()
    // For HTTPS tunnel, should use path only
    assert.include(messageStr, 'GET /api/endpoint HTTP/1.1')
  }).tags(['@core-engine2', '@message-builder', '@proxy', '@tunnel'])
})

test.group('CoreEngine - Authentication Manager Integration', () => {
  test('handles NTLM authentication correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/auth/ntlm/resource`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      authorization: [
        {
          kind: RequestAuthorizationKind,
          enabled: true,
          valid: true,
          type: 'ntlm',
          config: {
            username: 'u1',
            password: 'u2',
            domain: 'custom.com',
          },
        },
      ],
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()
    const response = log.response as IResponse
    assert.equal(response.status, 200, 'has the 200 status code')
  }).tags(['@core-engine2', '@auth-manager', '@ntlm'])

  test('handles Basic authentication correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/auth/basic/resource`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      authorization: [
        {
          kind: RequestAuthorizationKind,
          enabled: true,
          valid: true,
          type: 'basic',
          config: {
            username: 'testuser',
            password: 'testpass',
          },
        },
      ],
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()
    const response = log.response as IResponse
    assert.equal(response.status, 200, 'has the 200 status code')
  }).tags(['@core-engine2', '@auth-manager', '@basic'])

  test('handles Bearer token authentication correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/auth/bearer/resource`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      authorization: [
        {
          kind: RequestAuthorizationKind,
          enabled: true,
          valid: true,
          type: 'bearer',
          config: {
            token: 'test-token-123',
          },
        },
      ],
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()
    const response = log.response as IResponse
    assert.equal(response.status, 200, 'has the 200 status code')
  }).tags(['@core-engine2', '@auth-manager', '@bearer'])

  test('handles proxy authentication correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      proxy: `http://localhost:${httpConfig.proxyHttpPort}`,
      proxyUsername: 'proxyuser',
      proxyPassword: 'proxypass',
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()
    const response = log.response as IResponse
    assert.equal(response.status, 200, 'has the 200 status code')
  }).tags(['@core-engine2', '@auth-manager', '@proxy-auth'])
})

test.group('CoreEngine - Connection Manager Integration', () => {
  test('establishes direct HTTP connections correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
  }).tags(['@core-engine2', '@connection-manager', '@direct'])

  test('establishes direct HTTPS connections correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `https://localhost:${httpConfig.httpsPort}/v1/get`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      validateCertificates: false,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
  }).tags(['@core-engine2', '@connection-manager', '@direct-https'])

  test('establishes proxy HTTP connections correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      proxy: `http://localhost:${httpConfig.httpPort}`,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
  }).tags(['@core-engine2', '@connection-manager', '@proxy'])

  test('establishes HTTPS tunnel connections correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `https://localhost:${httpConfig.httpsPort}/v1/get`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      proxy: `http://localhost:${httpConfig.proxyHttpPort}`,
      validateCertificates: false,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
  }).tags(['@core-engine2', '@connection-manager', '@tunnel'])
})

test.group('CoreEngine - Error Handling Integration', () => {
  test('handles connection timeouts with proper error types', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `https://localhost:${httpConfig.httpsPort}/v1/get`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      timeout: 100, // Very short timeout
    }
    const engine = new CoreEngine(request, opts)

    try {
      await engine.send()
      assert.fail('Should have thrown a timeout error')
    } catch (error: unknown) {
      assert.isTrue(error instanceof Error)
      if (error instanceof Error) {
        assert.include(error.message, 'timeout')
      }
    }
  }).tags(['@core-engine2', '@error-handling', '@timeout'])

  test('handles connection errors with proper error types', async ({ assert }) => {
    const request: IHttpRequest = {
      url: 'http://invalid-hostname-that-does-not-exist-12345.com/api',
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      timeout: 5000,
    }
    const engine = new CoreEngine(request, opts)

    try {
      await engine.send()
      assert.fail('Should have thrown a connection error')
    } catch (error: unknown) {
      assert.isTrue(error instanceof Error)
      if (error instanceof Error) {
        assert.include(error.message, 'connect')
      }
    }
  }).tags(['@core-engine2', '@error-handling', '@connection'])

  test('handles unsupported payload types with proper error types', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/api/endpoint`,
      method: 'POST',
      payload: {
        type: 'string',
        data: 'test',
      },
    }
    const opts: HttpEngineOptions = {
      logger,
    }
    const engine = new CoreEngine(request, opts)

    try {
      await engine.prepareMessage()
      // This should not throw since 'string' is a valid payload type
      assert.ok(true, 'String payload type should be supported')
    } catch (error: unknown) {
      assert.isTrue(error instanceof Error)
      if (error instanceof Error) {
        assert.include(error.message, 'Unsupported payload type')
      }
    }
  }).tags(['@core-engine2', '@error-handling', '@payload'])
})

test.group('CoreEngine - Complete Integration Tests', () => {
  test('performs complete HTTP request with all components', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/get?a=b`,
      method: 'GET',
      headers: 'x-custom: true',
    }
    const opts: HttpEngineOptions = {
      logger,
      timeout: 10000,
      followRedirects: true,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
    assert.strictEqual(response.statusText, 'OK', 'has the response status text')
    assert.isNotEmpty(response.headers, 'has the response headers')
    assert.ok(response.payload, 'has the payload')

    const payload = (await response.readPayload('buffer')) as Buffer
    const bodyStr = payload.toString('utf8')
    const body = JSON.parse(bodyStr)

    assert.equal(body.headers['x-custom'], 'true', 'passes request headers')
    assert.equal(body.method, 'GET', 'passes the method')
    assert.deepEqual(body.query, { a: 'b' }, 'passes the query parameters')

    const timings = response.timings as RequestTime
    assert.isAtLeast(timings.connect, 0, 'has the timings.connect')
    assert.isAtLeast(timings.receive, 0, 'has the timings.receive')
    assert.isAtLeast(timings.send, 0, 'has the timings.send')
    if (timings.wait !== undefined && timings.wait >= 0) {
      assert.isAtLeast(response.loadingTime, timings.wait, 'has the timings.wait')
    }
  }).tags(['@core-engine2', '@integration', '@complete'])

  test('performs complete HTTPS request with all components', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `https://localhost:${httpConfig.httpsPort}/v1/get?a=b`,
      method: 'GET',
      headers: 'x-custom: true',
    }
    const opts: HttpEngineOptions = {
      logger,
      timeout: 10000,
      followRedirects: true,
      validateCertificates: false,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
    assert.strictEqual(response.statusText, 'OK', 'has the response status text')
    assert.isNotEmpty(response.headers, 'has the response headers')
    assert.ok(response.payload, 'has the payload')

    const payload = (await response.readPayload('buffer')) as Buffer
    const bodyStr = payload.toString('utf8')
    const body = JSON.parse(bodyStr)

    assert.equal(body.headers['x-custom'], 'true', 'passes request headers')
    assert.equal(body.method, 'GET', 'passes the method')
    assert.deepEqual(body.query, { a: 'b' }, 'passes the query parameters')

    const timings = response.timings as RequestTime
    assert.isAtLeast(timings.connect, 0, 'has the timings.connect')
    assert.isAtLeast(timings.receive, 0, 'has the timings.receive')
    assert.isAtLeast(timings.send, 0, 'has the timings.send')
    if (timings.wait !== undefined) {
      assert.isAtLeast(response.loadingTime, timings.wait, 'has the timings.wait')
    }
    assert.isAtLeast(timings.ssl!, 0, 'has the timings.ssl')
  }).tags(['@core-engine2', '@integration', '@complete', '@https'])

  test('performs POST request with payload', async ({ assert, httpConfig }) => {
    const sentBody = JSON.stringify({ test: true, data: 'value' })
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/post`,
      method: 'POST',
      headers: 'content-type: application/json\nx-custom: true',
      payload: sentBody,
    }
    const opts: HttpEngineOptions = {
      logger,
      timeout: 10000,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
    assert.strictEqual(response.statusText, 'OK', 'has the response status text')

    const payload = (await response.readPayload('buffer')) as Buffer
    const bodyStr = payload.toString('utf8')
    const body = JSON.parse(bodyStr)

    assert.equal(body.method, 'POST', 'passes the method')
    assert.equal(body.headers['content-type'], 'application/json', 'passes content-type')
    assert.equal(body.headers['x-custom'], 'true', 'passes custom headers')
    assert.deepEqual(body.body, { test: true, data: 'value' }, 'passes the payload')
  }).tags(['@core-engine2', '@integration', '@post'])

  test('handles redirects correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/redirect`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
      followRedirects: true,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the final response status code')

    // Should have redirect information
    assert.isArray(log.redirects, 'has redirects array')
    if (log.redirects) {
      assert.isAbove(log.redirects.length, 0, 'has at least one redirect')
    }
  }).tags(['@core-engine2', '@integration', '@redirects'])

  test('handles chunked responses correctly', async ({ assert, httpConfig }) => {
    const request: IHttpRequest = {
      url: `http://localhost:${httpConfig.httpPort}/v1/chunked`,
      method: 'GET',
    }
    const opts: HttpEngineOptions = {
      logger,
    }
    const engine = new CoreEngine(request, opts)
    const log = await engine.send()

    assert.ok(log, 'has the response')
    const response = new Response(log.response as IResponse)
    assert.strictEqual(response.status, 200, 'has the response status code')
    assert.ok(response.payload, 'has the payload')

    const payload = (await response.readPayload('buffer')) as Buffer
    const bodyStr = payload.toString('utf8')
    assert.isNotEmpty(bodyStr, 'has chunked response body')
  }).tags(['@core-engine2', '@integration', '@chunked'])
})

test.group('CoreEngine - Component Isolation Tests', () => {
  test('MessageBuilder creates correct HTTP messages', async ({ assert }) => {
    const logger = createLogger()
    const request: IHttpRequest = {
      url: 'http://localhost:8080/api/endpoint?query=param',
      method: 'POST',
      headers: 'content-type: text/plain',
    }

    const messageBuilder = new MessageBuilder({
      logger,
      request,
      hasProxy: false,
      isProxyTunnel: false,
      hostHeader: 'localhost:8080',
    })

    const headers = new Headers('content-type: text/plain')
    const message = messageBuilder.buildMessage(headers)

    assert.isTrue(message instanceof Buffer)
    const messageStr = message.toString()
    assert.include(messageStr, 'POST /api/endpoint?query=param HTTP/1.1')
    assert.include(messageStr, 'Host: localhost:8080')
    assert.include(messageStr, 'content-type: text/plain')
  }).tags(['@core-engine2', '@component-isolation', '@message-builder'])

  test('AuthManager handles NTLM authentication', async ({ assert }) => {
    const logger = createLogger()
    const headers = new Headers()

    const authManager = new AuthManager({
      logger,
      credentials: {
        username: 'testuser',
        password: 'testpass',
      },
    })

    const authConfig = {
      kind: RequestAuthorizationKind,
      enabled: true,
      type: 'ntlm',
      config: {
        username: 'testuser',
        password: 'testpass',
        domain: 'test.com',
      },
    }

    authManager.handleRequestAuthorization(headers, [authConfig])
    assert.isTrue(headers.has('Authorization'))
    const authHeader = headers.get('Authorization')
    assert.include(authHeader, 'NTLM ')
  }).tags(['@core-engine2', '@component-isolation', '@auth-manager'])

  test('ConnectionManager creates correct connection types', async ({ assert }) => {
    const logger = createLogger()
    const stats = {
      connectionTime: 0,
      lookupTime: 0,
      connectedTime: 0,
      secureStartTime: 0,
      secureConnectedTime: 0,
    }

    const connectionManager = new ConnectionManager(logger, stats)

    assert.ok(connectionManager, 'ConnectionManager created successfully')
    // Note: We can't actually test connection creation without a real server
    // but we can verify the manager is properly instantiated
  }).tags(['@core-engine2', '@component-isolation', '@connection-manager'])

  test('Error system creates proper error types', async ({ assert }) => {
    // Test connection error
    const connectionError = HttpEngineErrorFactory.connectionError('Connection failed', 111)
    assert.equal(connectionError.category, 'connection')
    assert.equal(connectionError.connectionType, 'direct')

    // Test authentication error
    const authError = HttpEngineErrorFactory.authenticationError('Auth failed', 401, 'ntlm')
    assert.equal(authError.category, 'authentication')
    assert.equal(authError.authType, 'ntlm')

    // Test timeout error
    const timeoutError = HttpEngineErrorFactory.connectionTimeout(30000)
    assert.equal(timeoutError.category, 'timeout')
    assert.equal(timeoutError.timeoutType, 'connection')
  }).tags(['@core-engine2', '@component-isolation', '@error-system'])
})
