import { Elysia } from 'elysia'
import express from 'express'
import { Hono } from 'hono'
import { describe, expect, test } from 'vp/test'

import { createServer } from '../../test/utils.js'
import * as Handler from './Handler.js'

describe('from', () => {
  describe('cors', () => {
    test('default: adds CORS headers', async () => {
      const handler = Handler.from()
      handler.get('/test', () => new Response('test'))

      const response = await handler.fetch(new Request('http://localhost/test'))

      expect(response.status).toBe(200)
      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
      expect(response.headers.get('Access-Control-Allow-Methods')).toBe(
        'GET, POST, PUT, DELETE, OPTIONS',
      )
      expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type')
    })

    test('behavior: cors = false disables CORS headers', async () => {
      const handler = Handler.from({ cors: false })
      handler.get('/test', () => new Response('test'))

      const response = await handler.fetch(new Request('http://localhost/test'))

      expect(response.status).toBe(200)
      expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull()
      expect(response.headers.get('Access-Control-Allow-Methods')).toBeNull()
    })

    test('behavior: custom cors config', async () => {
      const handler = Handler.from({
        cors: {
          origin: 'https://example.com',
          methods: 'GET, POST',
          headers: 'Content-Type, Authorization',
          credentials: true,
          maxAge: 86400,
        },
      })
      handler.get('/test', () => new Response('test'))

      const response = await handler.fetch(new Request('http://localhost/test'))

      expect(response.status).toBe(200)
      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://example.com')
      expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET, POST')
      expect(response.headers.get('Access-Control-Allow-Headers')).toBe(
        'Content-Type, Authorization',
      )
      expect(response.headers.get('Access-Control-Allow-Credentials')).toBe('true')
      expect(response.headers.get('Access-Control-Max-Age')).toBe('86400')
    })

    test('behavior: cors with array of origins', async () => {
      const handler = Handler.from({
        cors: {
          origin: ['https://example.com', 'https://other.com'],
        },
      })
      handler.get('/test', () => new Response('test'))

      const response = await handler.fetch(new Request('http://localhost/test'))

      expect(response.headers.get('Access-Control-Allow-Origin')).toBe(
        'https://example.com, https://other.com',
      )
    })

    test('behavior: OPTIONS preflight with default CORS', async () => {
      const handler = Handler.from()
      handler.get('/test', () => new Response('test'))

      const response = await handler.fetch(
        new Request('http://localhost/test', { method: 'OPTIONS' }),
      )

      expect(response.status).toBe(200)
      expect(await response.text()).toBe('')
      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
      expect(response.headers.get('Access-Control-Allow-Methods')).toBe(
        'GET, POST, PUT, DELETE, OPTIONS',
      )
    })

    test('behavior: custom headers override CORS headers', async () => {
      const handler = Handler.from({
        cors: { origin: 'https://default.com' },
        headers: { 'Access-Control-Allow-Origin': 'https://override.com' },
      })
      handler.get('/test', () => new Response('test'))

      const response = await handler.fetch(new Request('http://localhost/test'))

      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://override.com')
    })
  })
})

describe('compose', () => {
  test('default', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => new Response('test'))
    const handler2 = Handler.from()
    handler2.get('/test2', () => new Response('test2'))

    const handler = Handler.compose([handler1, handler2])
    expect(handler).toBeDefined()

    {
      const response = await handler.fetch(new Request('http://localhost/test'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test')
    }

    {
      const response = await handler.fetch(new Request('http://localhost/test2'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test2')
    }
  })

  test('behavior: path', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => new Response('test'))
    const handler2 = Handler.from()
    handler2.get('/test2', () => new Response('test2'))

    const handler = Handler.compose([handler1, handler2], {
      path: '/api',
    })
    expect(handler).toBeDefined()

    {
      const response = await handler.fetch(new Request('http://localhost/api/test'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test')
    }

    {
      const response = await handler.fetch(new Request('http://localhost/api/test2'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test2')
    }
  })

  test('behavior: headers', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => new Response('test'))
    const handler2 = Handler.from()
    handler2.get('/test2', () => new Response('test2'))

    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    })

    const handler = Handler.compose([handler1, handler2], {
      headers,
    })

    {
      const response = await handler.fetch(new Request('http://localhost/test'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test')
      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
      expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
      expect(response.headers.get('Access-Control-Allow-Headers')).toBe(
        'Content-Type, Authorization',
      )
    }

    {
      const response = await handler.fetch(new Request('http://localhost/test2'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test2')
      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    }
  })

  test('behavior: headers + path', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => new Response('test'))
    const handler2 = Handler.from()
    handler2.get('/test2', () => new Response('test2'))

    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
    })

    const handler = Handler.compose([handler1, handler2], {
      headers,
      path: '/api',
    })

    {
      const response = await handler.fetch(new Request('http://localhost/api/test'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test')
      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
      expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
    }

    {
      const response = await handler.fetch(new Request('http://localhost/api/test2'))
      expect(response.status).toBe(200)
      expect(await response.text()).toBe('test2')
      expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    }
  })

  test('behavior: headers + OPTIONS', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => new Response('test'))

    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Access-Control-Max-Age': '86400',
    })

    const handler = Handler.compose([handler1], {
      headers,
    })

    const response = await handler.fetch(
      new Request('http://localhost/test', {
        method: 'OPTIONS',
      }),
    )

    expect(response.status).toBe(200)
    expect(await response.text()).toBe('')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
    expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization')
    expect(response.headers.get('Access-Control-Max-Age')).toBe('86400')
  })

  test('behavior: headers + 404', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => new Response('test'))

    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
    })

    const handler = Handler.compose([handler1], {
      headers,
    })

    const response = await handler.fetch(new Request('http://localhost/nonexistent'))

    expect(response.status).toBe(404)
    expect(await response.text()).toBe('Not Found')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
  })

  test('behavior: headers propagation from child handlers', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => {
      const response = new Response('test')
      response.headers.set('X-Custom-Header', 'custom-value')
      return response
    })

    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
    })

    const handler = Handler.compose([handler1], {
      headers,
    })

    const response = await handler.fetch(new Request('http://localhost/test'))

    expect(response.status).toBe(200)
    expect(await response.text()).toBe('test')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('X-Custom-Header')).toBe('custom-value')
  })

  test('behavior: headers with child handler headers', async () => {
    const childHeaders = new Headers({
      'X-Child-Header': 'child-value',
    })
    const handler1 = Handler.from({ headers: childHeaders })
    handler1.get('/test', () => new Response('test'))

    const parentHeaders = new Headers({
      'Access-Control-Allow-Origin': '*',
      'X-Parent-Header': 'parent-value',
    })

    const handler = Handler.compose([handler1], {
      headers: parentHeaders,
    })

    const response = await handler.fetch(new Request('http://localhost/test'))

    expect(response.status).toBe(200)
    expect(await response.text()).toBe('test')
    // Both parent and child headers should be present
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('X-Parent-Header')).toBe('parent-value')
    expect(response.headers.get('X-Child-Header')).toBe('child-value')
  })

  test('behavior: headers as object', async () => {
    const handler1 = Handler.from()
    handler1.get('/test', () => new Response('test'))

    const handler = Handler.compose([handler1], {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      },
    })

    const response = await handler.fetch(new Request('http://localhost/test'))
    expect(response.status).toBe(200)
    expect(await response.text()).toBe('test')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
    expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization')
  })

  describe('integration', () => {
    const handler1 = Handler.from()
    handler1.get('/foo', () => new Response('foo'))
    handler1.post('/bar', () => new Response('bar'))

    const handler2 = Handler.from()
    handler2.get('/baz', () => new Response('baz'))
    handler2.post('/qux', () => new Response('qux'))

    const handler = Handler.compose([handler1, handler2], {
      path: '/api',
    })

    test('hono', async () => {
      const app = new Hono()
      app.all('*', (c) => handler.fetch(c.req.raw))

      {
        const response = await app.request('/api/foo')
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await app.request('/api/bar', {
          method: 'POST',
        })
        expect(await response.text()).toBe('bar')
      }

      {
        const response = await app.request('/api/baz', {
          method: 'GET',
        })
        expect(await response.text()).toBe('baz')
      }

      {
        const response = await app.request('/api/qux', {
          method: 'POST',
        })
        expect(await response.text()).toBe('qux')
      }
    })

    test('elysia', async () => {
      const app = new Elysia().all('*', ({ request }) => handler.fetch(request))

      {
        const response = await app.handle(new Request('http://localhost/api/foo'))
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await app.handle(
          new Request('http://localhost/api/bar', {
            method: 'POST',
          }),
        )
        expect(await response.text()).toBe('bar')
      }

      {
        const response = await app.handle(
          new Request('http://localhost/api/baz', {
            method: 'GET',
          }),
        )
        expect(await response.text()).toBe('baz')
      }

      {
        const response = await app.handle(
          new Request('http://localhost/api/qux', {
            method: 'POST',
          }),
        )
        expect(await response.text()).toBe('qux')
      }
    })

    test('node.js', async () => {
      const server = await createServer(handler.listener)

      {
        const response = await fetch(`${server.url}/api/foo`)
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await fetch(`${server.url}/api/bar`, {
          method: 'POST',
        })
        expect(await response.text()).toBe('bar')
      }

      {
        const response = await fetch(`${server.url}/api/baz`, {
          method: 'GET',
        })
        expect(await response.text()).toBe('baz')
      }

      {
        const response = await fetch(`${server.url}/api/qux`, {
          method: 'POST',
        })
        expect(await response.text()).toBe('qux')
      }

      await server.closeAsync()
    })

    test('express', async () => {
      const app = express()
      app.use(handler.listener)

      const server = await createServer(app)

      {
        const response = await fetch(`${server.url}/api/foo`)
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await fetch(`${server.url}/api/bar`, {
          method: 'POST',
        })
        expect(await response.text()).toBe('bar')
      }

      {
        const response = await fetch(`${server.url}/api/baz`, {
          method: 'GET',
        })
        expect(await response.text()).toBe('baz')
      }

      {
        const response = await fetch(`${server.url}/api/qux`, {
          method: 'POST',
        })
        expect(await response.text()).toBe('qux')
      }

      await server.closeAsync()
    })
  })
})

describe('from', () => {
  test('default', () => {
    const handler = Handler.from()
    expect(handler).toBeDefined()
  })

  test('.fetch', async () => {
    const handler = Handler.from()
    handler.get('/test', () => new Response('test'))

    const response = await handler.fetch(new Request('http://localhost/test'))
    expect(response.status).toBe(200)
    expect(await response.text()).toBe('test')
  })

  test('.listener', async () => {
    const handler = Handler.from()
    handler.get('/test', () => Response.json({ message: 'hello from listener' }))

    const server = await createServer(handler.listener)

    // Make a request to the server
    const response = await fetch(`${server.url}/test`)
    expect(response.status).toBe(200)

    const data = await response.json()
    expect(data).toEqual({ message: 'hello from listener' })
  })

  test('behavior: headers', async () => {
    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    })

    const handler = Handler.from({ headers })
    handler.get('/test', () => new Response('test'))

    const response = await handler.fetch(new Request('http://localhost/test'))
    expect(response.status).toBe(200)
    expect(await response.text()).toBe('test')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
    expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization')
  })

  test('behavior: headers + OPTIONS', async () => {
    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Access-Control-Max-Age': '86400',
    })

    const handler = Handler.from({ headers })
    handler.get('/test', () => new Response('test'))

    const response = await handler.fetch(
      new Request('http://localhost/test', {
        method: 'OPTIONS',
      }),
    )

    expect(response.status).toBe(200)
    expect(await response.text()).toBe('')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
    expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization')
    expect(response.headers.get('Access-Control-Max-Age')).toBe('86400')
  })

  test('behavior: headers + 404', async () => {
    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
    })

    const handler = Handler.from({ headers })
    handler.get('/test', () => new Response('test'))

    const response = await handler.fetch(new Request('http://localhost/nonexistent'))

    expect(response.status).toBe(404)
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
  })

  test('behavior: headers propagation from routes', async () => {
    const headers = new Headers({
      'Access-Control-Allow-Origin': '*',
    })

    const handler = Handler.from({ headers })
    handler.get('/test', () => {
      const response = new Response('test')
      response.headers.set('X-Custom-Header', 'custom-value')
      response.headers.set('Content-Type', 'text/plain')
      return response
    })

    const response = await handler.fetch(new Request('http://localhost/test'))

    expect(response.status).toBe(200)
    expect(await response.text()).toBe('test')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('X-Custom-Header')).toBe('custom-value')
    expect(response.headers.get('Content-Type')).toBe('text/plain')
  })

  test('behavior: headers as object', async () => {
    const handler = Handler.from({
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'POST, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      },
    })
    handler.get('/test', () => new Response('test'))

    const response = await handler.fetch(new Request('http://localhost/test'))
    expect(response.status).toBe(200)
    expect(await response.text()).toBe('test')
    expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*')
    expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
    expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type, Authorization')
  })

  describe('integration', () => {
    const handler = Handler.from()
    handler.get('/foo', () => new Response('foo'))
    handler.post('/bar', () => new Response('bar'))

    test('hono', async () => {
      const app = new Hono()
      app.all('*', (c) => handler.fetch(c.req.raw))

      {
        const response = await app.request('/foo')
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await app.request('/bar', {
          method: 'POST',
        })
        expect(await response.text()).toBe('bar')
      }
    })

    test('elysia', async () => {
      const app = new Elysia().all('*', ({ request }) => handler.fetch(request))

      {
        const response = await app.handle(new Request('http://localhost/foo'))
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await app.handle(
          new Request('http://localhost/bar', {
            method: 'POST',
          }),
        )
        expect(await response.text()).toBe('bar')
      }
    })

    test('node.js', async () => {
      const server = await createServer(handler.listener)

      {
        const response = await fetch(`${server.url}/foo`)
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await fetch(`${server.url}/bar`, {
          method: 'POST',
        })
        expect(await response.text()).toBe('bar')
      }

      await server.closeAsync()
    })

    test('express', async () => {
      const app = express()
      app.use(handler.listener)

      const server = await createServer(app)

      {
        const response = await fetch(`${server.url}/foo`)
        expect(await response.text()).toBe('foo')
      }

      {
        const response = await fetch(`${server.url}/bar`, {
          method: 'POST',
        })
        expect(await response.text()).toBe('bar')
      }

      await server.closeAsync()
    })
  })
})
