import { test, expect } from 'vitest'
import { PluginImplementation } from './implementation'

const createPlugin = () =>
  new PluginImplementation({
    actions: {
      sayHello: async () => {
        return 'Hello'
      },
    },
  }).initialize({
    alias: 'pluggy',
    configuration: {},
    interfaces: {
      'plugin-creatable1': {
        integrationAlias: 'github1',
        integrationInterfaceAlias: 'creatable-pr',
        name: 'github',
        version: '0.0.0',
        actions: {},
        events: {
          itemCreated: { name: 'prOpened' },
        },
        channels: {},
        entities: {},
      },
      'plugin-creatable2': {
        integrationAlias: 'github1',
        integrationInterfaceAlias: 'creatable-issue',
        name: 'github',
        version: '0.0.0',
        actions: {},
        events: {
          itemCreated: { name: 'issueOpened' },
        },
        channels: {},
        entities: {},
      },
    },
    integrations: {
      'plugin-github': {
        integrationAlias: 'github1',
        name: 'github',
        version: '0.0.0',
      },
    },
  })

test('getting action handler returns action handler', () => {
  const plugin = createPlugin()

  const actionHandler = plugin.actionHandlers.sayHello!
  expect(actionHandler.name).toEqual('sayHello')
})

test('getting text message handlers also returns global handlers', () => {
  const plugin = createPlugin()

  plugin.on.message('text', async function foo() {})
  plugin.on.message('*', async function bar() {})

  const textHandlers = plugin.messageHandlers['text']
  expect(textHandlers?.map((handler) => handler.name)).toEqual(['foo', 'bar'])
})

test('getting global message handlers only returns global handlers once', () => {
  const plugin = createPlugin()

  plugin.on.message('text', async function foo() {})
  plugin.on.message('*', async function bar() {})

  const commonHandlers = plugin.messageHandlers['*']
  expect(commonHandlers?.map((handler) => handler.name)).toEqual(['bar'])
})

test('getting foo event handlers also returns global handlers', () => {
  const plugin = createPlugin()

  plugin.on.event('foo', async function foo() {})
  plugin.on.event('*', async function bar() {})

  const fooHandlers = plugin.eventHandlers['foo']
  expect(fooHandlers?.map((handler) => handler.name)).toEqual(['foo', 'bar'])
})

test('getting global event handlers only returns global handlers once', () => {
  const plugin = createPlugin()

  plugin.on.event('foo', async function foo() {})
  plugin.on.event('*', async function bar() {})

  const commonHandlers = plugin.eventHandlers['*']
  expect(commonHandlers?.map((handler) => handler.name)).toEqual(['bar'])
})

test('getting creatable:itemCreated event handlers also returns interface handlers', () => {
  const plugin = createPlugin()

  // Directly listen on an event of an integration within the bot:
  plugin.on.event('github1:prOpened', async function handler1() {})
  // Listen on an event of an interface within the plugin:
  plugin.on.event('plugin-creatable1:itemCreated', async function handler2() {})
  // Directly listen on another event of an integration within the bot:
  plugin.on.event('github1:issueOpened', async function handler3() {})
  // Listen on an event of another interface within the plugin:
  plugin.on.event('plugin-creatable2:itemCreated', async function handler4() {})

  const fooCreatedHandlers = plugin.eventHandlers['github1:prOpened']
  expect(fooCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2'])

  const itemCreatedHandlers = plugin.eventHandlers['plugin-creatable1:itemCreated']
  expect(itemCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler2'])

  const foo2CreatedHandlers = plugin.eventHandlers['github1:issueOpened']
  expect(foo2CreatedHandlers?.map((handler) => handler.name)).toEqual(['handler3', 'handler4'])

  const item2CreatedHandlers = plugin.eventHandlers['plugin-creatable2:itemCreated']
  expect(item2CreatedHandlers?.map((handler) => handler.name)).toEqual(['handler4'])
})

test('getting integration:prOpened event handlers returns integration handlers', () => {
  const plugin = createPlugin()

  // Directly listen on an event of an integration within the bot:
  plugin.on.event('github1:prOpened', async function handler1() {})
  // Listen on an event of an aliased integration within the plugin:
  plugin.on.event('plugin-github:prOpened', async function handler2() {})

  const itemCreatedHandlers = plugin.eventHandlers['github1:prOpened']
  expect(itemCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2'])

  const fooCreatedHandlers = plugin.eventHandlers['plugin-github:prOpened']
  expect(fooCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler2'])
})

test('getting foo stateExpired handlers also returns global handlers', () => {
  const plugin = createPlugin()

  plugin.on.stateExpired('foo', async function foo() {})
  plugin.on.stateExpired('*', async function bar() {})

  const fooHandlers = plugin.stateExpiredHandlers['foo']
  expect(fooHandlers?.map((handler) => handler.name)).toEqual(['foo', 'bar'])
})

test('getting global stateExpired handlers only returns global handlers once', () => {
  const plugin = createPlugin()

  plugin.on.stateExpired('foo', async function foo() {})
  plugin.on.stateExpired('*', async function bar() {})

  const commonHandlers = plugin.stateExpiredHandlers['*']
  expect(commonHandlers?.map((handler) => handler.name)).toEqual(['bar'])
})

test('getting foo before_incoming_event hook handlers also returns global handlers', () => {
  const plugin = createPlugin()

  plugin.on.beforeIncomingEvent('foo', async function foo() {
    return undefined
  })
  plugin.on.beforeIncomingEvent('*', async function bar() {
    return undefined
  })

  const fooHandlers = plugin.hookHandlers.before_incoming_event['foo']
  expect(fooHandlers?.map((handler) => handler.name)).toEqual(['foo', 'bar'])
})

test('getting global before_incoming_event hook handlers only returns global handlers once', () => {
  const plugin = createPlugin()

  plugin.on.beforeIncomingEvent('foo', async function foo() {
    return undefined
  })
  plugin.on.beforeIncomingEvent('*', async function bar() {
    return undefined
  })

  const commonHandlers = plugin.hookHandlers.before_incoming_event['*']
  expect(commonHandlers?.map((handler) => handler.name)).toEqual(['bar'])
})

test('getting creatable:itemCreated before_incoming_event hook handlers also returns interface handlers', () => {
  const plugin = createPlugin()

  plugin.on.beforeIncomingEvent('github1:prOpened', async function handler1() {
    return undefined
  })
  plugin.on.beforeIncomingEvent('plugin-creatable1:itemCreated', async function handler2() {
    return undefined
  })

  const fooCreatedHandlers = plugin.hookHandlers.before_incoming_event['github1:prOpened']
  expect(fooCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2'])

  const itemCreatedHandlers = plugin.hookHandlers.before_incoming_event['plugin-creatable1:itemCreated']
  expect(itemCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler2'])
})

test('getting foo after_incoming_event hook handlers also returns global handlers', () => {
  const plugin = createPlugin()

  plugin.on.afterIncomingEvent('foo', async function foo() {
    return undefined
  })
  plugin.on.afterIncomingEvent('*', async function bar() {
    return undefined
  })

  const fooHandlers = plugin.hookHandlers.after_incoming_event['foo']
  expect(fooHandlers?.map((handler) => handler.name)).toEqual(['foo', 'bar'])
})

test('getting global after_incoming_event hook handlers only returns global handlers once', () => {
  const plugin = createPlugin()

  plugin.on.afterIncomingEvent('foo', async function foo() {
    return undefined
  })
  plugin.on.afterIncomingEvent('*', async function bar() {
    return undefined
  })

  const commonHandlers = plugin.hookHandlers.after_incoming_event['*']
  expect(commonHandlers?.map((handler) => handler.name)).toEqual(['bar'])
})

test('getting creatable:itemCreated after_incoming_event hook handlers also returns interface handlers', () => {
  const plugin = createPlugin()

  plugin.on.afterIncomingEvent('github1:prOpened', async function handler1() {
    return undefined
  })
  plugin.on.afterIncomingEvent('plugin-creatable1:itemCreated', async function handler2() {
    return undefined
  })

  const fooCreatedHandlers = plugin.hookHandlers.after_incoming_event['github1:prOpened']
  expect(fooCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2'])

  const itemCreatedHandlers = plugin.hookHandlers.after_incoming_event['plugin-creatable1:itemCreated']
  expect(itemCreatedHandlers?.map((handler) => handler.name)).toEqual(['handler2'])
})

test('getting message handlers respects register order', () => {
  const plugin = createPlugin()

  plugin.on.message('*', async function handler1() {
    return undefined
  })
  plugin.on.message('text', async function handler2() {
    return undefined
  })

  const handlers = plugin.messageHandlers['text']
  expect(handlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2'])
})

test('getting event handlers respects register order', () => {
  const plugin = createPlugin()

  plugin.on.event('*', async function handler1() {})
  plugin.on.event('plugin-creatable1:itemCreated', async function handler2() {})
  plugin.on.event('github1:prOpened', async function handler3() {})

  const handlers = plugin.eventHandlers['github1:prOpened']
  expect(handlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2', 'handler3'])
})

test('getting state handlers respects register order', () => {
  const plugin = createPlugin()

  plugin.on.stateExpired('*', async function handler1() {
    return undefined
  })
  plugin.on.stateExpired('myState', async function handler2() {
    return undefined
  })

  const handlers = plugin.stateExpiredHandlers['myState']
  expect(handlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2'])
})

test('getting hook handlers respects register order', () => {
  const plugin = createPlugin()

  plugin.on.beforeIncomingEvent('*', async function handler1() {
    return undefined
  })
  plugin.on.beforeIncomingEvent('plugin-creatable1:itemCreated', async function handler2() {
    return undefined
  })
  plugin.on.beforeIncomingEvent('github1:prOpened', async function handler3() {
    return undefined
  })

  const handlers = plugin.hookHandlers.before_incoming_event['github1:prOpened']
  expect(handlers?.map((handler) => handler.name)).toEqual(['handler1', 'handler2', 'handler3'])
})
