import {
  DataSteps,
  FlowResponseDataEnum,
  FlowSourceEnum,
  HttpAssertions as CoreAssertions,
  IHttpFlow,
} from '@api-client/core/browser.js'
import { assert, fixture, html } from '@open-wc/testing'
import { resetMouse, setViewport } from '@web/test-runner-commands'
import sinon from 'sinon'
import type HttpFlows from '../../../src/elements/http/HttpFlows.element.js'
import type UiButton from '../../../src/md/button/internals/button.js'
import type UiChip from '../../../src/md/chip/internals/Chip.js'
import type Input from '../../../src/md/input/Input.js'
import type UiListItem from '../../../src/md/list/internals/ListItem.js'
import { UiMock } from '../../helpers/UiMock.js'
import { SnackNotifications } from '../../../src/md/notification/SnackNotifications.js'
import '../../../src/define/http/http-flows.js'

describe('element', () => {
  describe('http', () => {
    before(async () => {
      await setViewport({ width: 1920, height: 1080 })
    })

    describe('HttpFlows', () => {
      async function basicFixture(): Promise<HttpFlows> {
        return fixture(html`<http-flows></http-flows>`)
      }

      async function emptyResponseFlowFixture(): Promise<HttpFlows> {
        const flows: IHttpFlow[] = [
          {
            actions: [],
            trigger: 'response',
            description: 'initial',
          },
        ]
        return fixture(html`<http-flows .model="${flows}"></http-flows>`)
      }

      describe('initialization', () => {
        it('has no flows UI', async () => {
          const editor = await basicFixture()
          const nodes = editor.shadowRoot!.querySelectorAll('.param-row')
          assert.lengthOf(nodes, 0, 'has no param-row nodes')
        })

        it('has the flow building blocks panel', async () => {
          const editor = await basicFixture()
          const node = editor.shadowRoot!.querySelector('.options')
          assert.ok(node)
        })

        it('has the empty message rendered', async () => {
          const editor = await basicFixture()
          const node = editor.shadowRoot!.querySelector('.empty-message')!
          assert.ok(node, 'has the node')
          assert.equal(node.textContent?.trim(), 'Create a flow to get started.')
        })

        it('has the add flow button', async () => {
          const editor = await basicFixture()
          const node = editor.shadowRoot!.querySelector('.empty-message .empty-button')!
          assert.ok(node, 'has the button')
        })
      })

      describe('adding flows', () => {
        let element: HttpFlows

        beforeEach(async () => {
          element = await basicFixture()
        })

        it('empty message button triggers the add flow dialog', async () => {
          const button = element.shadowRoot!.querySelector('.empty-message .empty-button') as UiButton
          button.click()
          assert.isTrue(element.flowAddDialog?.open, 'the dialog is opened')
        })

        it('creates a flow from the dialog', async () => {
          const button = element.shadowRoot!.querySelector('.empty-message .empty-button') as UiButton
          button.click()
          const dialog = element.flowAddDialog!
          await dialog.updateComplete
          const input = dialog.querySelector('ui-filled-text-field')!
          input.value = 'My Flow 1'
          input.notifyChange()
          const dialogButton = dialog.shadowRoot!.querySelector('.internal-button[value="confirm"]') as UiButton
          dialogButton.click()
          assert.lengthOf(element.model, 1, 'has the created flow')
          const [flow] = element.model
          assert.equal(flow.description, 'My Flow 1', 'the flow has the entered name')
        })

        it('adds the description when provided', () => {
          element.addFlow('test')
          assert.lengthOf(element.model, 1, 'has the created flow')
          const [flow] = element.model
          assert.equal(flow.description, 'test', 'the flow has the passed description')
        })

        it('adds the current trigger', () => {
          element.addFlow('test')
          const [flow] = element.model
          assert.equal(flow.trigger, 'response', 'has the current trigger')
        })

        it('adds empty actions', () => {
          element.addFlow()
          const [flow] = element.model
          assert.deepEqual(flow.actions, [])
        })

        it('sets the selectedFlow', () => {
          element.addFlow()
          assert.equal(element.selectedFlow, 0, 'selects the first flow')
          element.addFlow()
          assert.equal(element.selectedFlow, 1, 'selects another')
        })

        it('notifies change', () => {
          const spy = sinon.spy()
          element.addEventListener('change', spy)
          element.addFlow()
          assert.isTrue(spy.calledOnce)
        })

        it('changes the empty message after adding a flow', async () => {
          element.addFlow()
          await element.updateComplete
          const node = element.shadowRoot!.querySelector('.empty-message')!
          assert.ok(node, 'has the node')
          assert.equal(node.textContent?.trim(), 'Drop a condition or an action here')
        })

        it('adds a flow after changing the trigger', async () => {
          element.addFlow()
          await element.updateComplete
          element.trigger = 'request'
          element.addFlow()
          await element.updateComplete
          assert.lengthOf(element.model, 2, 'has 2 flows')
          const [f1, f2] = element.model
          assert.equal(f1.trigger, 'response', 'flows 1 has the response trigger')
          assert.equal(f2.trigger, 'request', 'flows 2 has the request trigger')
        })
      })

      describe('working with a condition', () => {
        let element: HttpFlows

        beforeEach(async () => {
          element = await emptyResponseFlowFixture()
        })

        afterEach(async () => {
          await resetMouse()
        })

        it('drops the condition onto the editor', async () => {
          const chip = element.shadowRoot!.querySelector(`ui-chip[data-kind="Core#Condition"]`) as UiChip

          const target = element.shadowRoot!.querySelector(`.editor`) as HTMLElement
          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete

          const condition = element.model[0].condition!
          assert.typeOf(condition, 'object', 'has added condition')
          assert.deepEqual(condition.steps, [], 'the condition has empty steps')
        })

        it('renders the condition UI', async () => {
          element.setCondition()
          await element.updateComplete

          const block = element.shadowRoot!.querySelector('.step-row.condition')!
          assert.ok(block, 'has the condition block')

          const label = block.querySelector('.action-tab > span')!
          assert.ok(label, 'has the label')
          assert.equal(label.textContent!.trim(), 'Condition', 'the label has the text value')
        })

        it('renders the condition dropdown menu', async () => {
          element.setCondition()
          await element.updateComplete

          const block = element.shadowRoot!.querySelector('.step-row.condition')!
          assert.ok(block, 'has the condition block')

          const list = block.querySelector('.action-tab > ui-dropdown-list')!
          assert.ok(list, 'has the dropdown')

          const trigger = list.querySelector('.condition-options-trigger')!
          assert.ok(trigger, 'has the dropdown trigger')
          assert.equal(trigger.localName, 'ui-icon-button', 'the dropdown trigger is the icon button')

          const listItems = Array.from(list.querySelectorAll('ui-list-item'))
          assert.lengthOf(listItems, 1, 'has all dropdown options')

          const [o1] = listItems
          assert.equal(o1.dataset.option, 'delete', 'has the remove item')
          assert.equal(o1.textContent!.trim(), 'Remove', 'the remove item has the text value')
        })

        it('disables the "condition" suggestion after adding one', async () => {
          element.setCondition()
          await element.updateComplete
          const chip = element.shadowRoot!.querySelector(`ui-chip[data-kind="Core#Condition"]`) as UiChip
          assert.isTrue(chip.disabled, 'the chip is disabled')
        })

        it('prohibits overriding the condition', async () => {
          element.setCondition()
          element.model[0].condition!.description = 'test'
          element.setCondition()
          assert.equal(element.model[0].condition!.description, 'test', 'the condition is unchanged')
        })

        it('notifies the user when overriding the condition', async () => {
          const spy = sinon.spy(SnackNotifications, 'notify')
          try {
            element.setCondition()
            await element.updateComplete
            element.setCondition()
          } finally {
            spy.restore()
          }
          assert.isTrue(spy.calledOnce, 'notifies the user')
          const message = spy.args[0][0] as string
          assert.equal(message, 'This flow already have a condition.')
        })

        it('drops data-source group onto the condition', async () => {
          element.setCondition()
          await element.updateComplete

          const chip = element.shadowRoot!.querySelector(
            `ui-chip[data-kind="${DataSteps.ReadDataSourceStepKind}"][data-data="${FlowResponseDataEnum.status}"]`
          ) as UiChip
          const target = element.shadowRoot!.querySelector(`.condition-row .steps`) as HTMLElement

          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete
          const condition = element.model[0].condition!
          assert.lengthOf(condition.steps, 1, 'the condition has the step')

          const step = condition.steps[0] as DataSteps.IReadDataSourceStep

          assert.equal(step.kind, DataSteps.ReadDataSourceStepKind, 'has the kind')
          assert.equal(step.source, FlowSourceEnum.response, 'has the source')
          assert.equal(step.data, FlowResponseDataEnum.status, 'has the data')

          const unit = target.querySelector('.step-unit')!
          assert.ok(unit, 'renders the step container')
          const spacer = unit.querySelector('.drop-target.spacer')!
          assert.ok(spacer, 'renders the drop target')
          const pill = unit.querySelector('.step-pill')!
          assert.ok(pill, 'renders the data pill')
          assert.equal(pill.textContent?.trim(), 'Status code', 'renders the data pill value')
          assert.isTrue(spacer.nextElementSibling === pill, 'render the drop target before the pill')
        })

        it('drops transformation group onto the condition', async () => {
          element.setCondition()
          await element.updateComplete

          const chip = element.shadowRoot!.querySelector(
            `ui-chip[data-kind="${DataSteps.ReadValueStepKind}"]`
          ) as UiChip
          const target = element.shadowRoot!.querySelector(`.condition-row .steps`) as HTMLElement

          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete
          const condition = element.model[0].condition!
          assert.lengthOf(condition.steps, 1, 'the condition has the step')

          const step = condition.steps[0] as DataSteps.IReadValueStep
          assert.equal(step.kind, DataSteps.ReadValueStepKind, 'has the kind')

          const unit = target.querySelector('.step-unit')!
          assert.ok(unit, 'renders the step container')
          const spacer = unit.querySelector('.drop-target.spacer')!
          assert.ok(spacer, 'renders the drop target')
          const pill = unit.querySelector('.step-pill')!
          assert.ok(pill, 'renders the data pill')
          assert.equal(pill.textContent?.trim(), 'Read value', 'renders the data pill value')
          assert.isTrue(spacer.nextElementSibling === pill, 'render the drop target before the pill')
          const input = unit.querySelector('ui-filled-text-field') as Input
          assert.ok(input, 'renders the path input')
        })

        it('drops assertion group onto the condition', async () => {
          element.setCondition()
          await element.updateComplete

          const chip = element.shadowRoot!.querySelector(
            `ui-chip[data-kind="${CoreAssertions.EqualAssertionKind}"]`
          ) as UiChip
          const target = element.shadowRoot!.querySelector(`.condition-row .steps`) as HTMLElement

          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete
          const condition = element.model[0].condition!
          assert.lengthOf(condition.steps, 1, 'the condition has the step')

          const step = condition.steps[0] as CoreAssertions.IEqualAssertion
          assert.equal(step.kind, CoreAssertions.EqualAssertionKind, 'has the kind')

          const unit = target.querySelector('.step-unit')!
          assert.ok(unit, 'renders the step container')
          const spacer = unit.querySelector('.drop-target.spacer')!
          assert.ok(spacer, 'renders the drop target')
          const input = unit.querySelector('ui-filled-text-field')!
          assert.ok(input, 'renders the value input')
        })

        it('does not allow action group onto the condition', async () => {
          element.setCondition()
          await element.updateComplete

          const chip = element.shadowRoot!.querySelector(
            `ui-chip[data-kind="${DataSteps.SetCookieStepKind}"]`
          ) as UiChip
          const target = element.shadowRoot!.querySelector(`.condition-row .steps`) as HTMLElement

          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete
          const condition = element.model[0].condition!
          assert.lengthOf(condition.steps, 0, 'has no steps')
        })

        it('removes the condition from the dropdown option', async () => {
          element.setCondition()
          await element.updateComplete

          const list = element.shadowRoot!.querySelector('.step-row.condition .action-tab > ui-dropdown-list')!
          assert.ok(list, 'has the dropdown')

          const option = list.querySelector('ui-list-item[data-option="delete"]') as UiListItem
          option.click()

          await element.updateComplete

          assert.isUndefined(element.model[0].condition)
        })
      })

      describe('Working with actions', () => {
        let element: HttpFlows

        beforeEach(async () => {
          element = await emptyResponseFlowFixture()
        })

        afterEach(async () => {
          await resetMouse()
        })

        it('drops the action onto the editor', async () => {
          const chip = element.shadowRoot!.querySelector(`ui-chip[data-kind="Core#Action"]`) as UiChip

          const target = element.shadowRoot!.querySelector(`.editor`) as HTMLElement
          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete

          const { actions } = element.model[0]
          assert.lengthOf(actions, 1, 'has the action')
          const [a1] = actions
          assert.deepEqual(a1.steps, [], 'actions steps are empty')
        })

        it('renders the action UI', async () => {
          element.addAction()
          await element.updateComplete

          const block = element.shadowRoot!.querySelector('.step-row')!
          assert.ok(block, 'has the action block')

          const label = block.querySelector('.action-tab > span')!
          assert.ok(label, 'has the label')
          assert.equal(label.textContent!.trim(), 'Action 1', 'the label has the text value')
        })

        it('renders the action dropdown menu', async () => {
          element.addAction()
          await element.updateComplete

          const block = element.shadowRoot!.querySelector('.step-row')!
          assert.ok(block, 'has the action block')

          const list = block.querySelector('.action-tab > ui-dropdown-list')!
          assert.ok(list, 'has the dropdown')

          const trigger = list.querySelector('.action-options-trigger')!
          assert.ok(trigger, 'has the dropdown trigger')
          assert.equal(trigger.localName, 'ui-icon-button', 'the dropdown trigger is the icon button')

          const listItems = Array.from(list.querySelectorAll('ui-list-item'))
          assert.lengthOf(listItems, 2, 'has all dropdown options')

          const [o1, o2] = listItems
          assert.equal(o1.dataset.option, 'rename', 'has the remove item')
          assert.equal(o1.textContent!.trim(), 'Rename', 'the remove item has the text value')
          assert.equal(o2.dataset.option, 'delete', 'has the remove item')
          assert.equal(o2.textContent!.trim(), 'Remove', 'the remove item has the text value')
        })

        it('removes the action from the dropdown option', async () => {
          element.addAction()
          await element.updateComplete

          const list = element.shadowRoot!.querySelector('.step-row .action-tab > ui-dropdown-list')!
          assert.ok(list, 'has the dropdown')

          const option = list.querySelector('ui-list-item[data-option="delete"]') as UiListItem
          option.click()

          await element.updateComplete

          assert.deepEqual(element.model[0].actions, [])
        })

        it('drops data-source group onto the action', async () => {
          element.addAction()
          await element.updateComplete

          const chip = element.shadowRoot!.querySelector(
            `ui-chip[data-kind="${DataSteps.ReadDataSourceStepKind}"][data-data="${FlowResponseDataEnum.status}"]`
          ) as UiChip
          const target = element.shadowRoot!.querySelector(`.action-row .steps`) as HTMLElement

          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete
          const action = element.model[0].actions[0]
          assert.lengthOf(action.steps, 1, 'the action has the step')

          const step = action.steps[0] as DataSteps.IReadDataSourceStep

          assert.equal(step.kind, DataSteps.ReadDataSourceStepKind, 'has the kind')
          assert.equal(step.source, FlowSourceEnum.response, 'has the source')
          assert.equal(step.data, FlowResponseDataEnum.status, 'has the data')

          const unit = target.querySelector('.step-unit')!
          assert.ok(unit, 'renders the step container')
          const spacer = unit.querySelector('.drop-target.spacer')!
          assert.ok(spacer, 'renders the drop target')
          const pill = unit.querySelector('.step-pill')!
          assert.ok(pill, 'renders the data pill')
          assert.equal(pill.textContent?.trim(), 'Status code', 'renders the data pill value')
          assert.isTrue(spacer.nextElementSibling === pill, 'render the drop target before the pill')
        })

        it('drops transformation group onto the action', async () => {
          element.addAction()
          await element.updateComplete

          const chip = element.shadowRoot!.querySelector(
            `ui-chip[data-kind="${DataSteps.ReadValueStepKind}"]`
          ) as UiChip
          const target = element.shadowRoot!.querySelector(`.action-row .steps`) as HTMLElement

          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete
          const action = element.model[0].actions[0]
          assert.lengthOf(action.steps, 1, 'the action has the step')

          const step = action.steps[0] as DataSteps.IReadValueStep
          assert.equal(step.kind, DataSteps.ReadValueStepKind, 'has the kind')

          const unit = target.querySelector('.step-unit')!
          assert.ok(unit, 'renders the step container')
          const spacer = unit.querySelector('.drop-target.spacer')!
          assert.ok(spacer, 'renders the drop target')
          const pill = unit.querySelector('.step-pill')!
          assert.ok(pill, 'renders the data pill')
          assert.equal(pill.textContent?.trim(), 'Read value', 'renders the data pill value')
          assert.isTrue(spacer.nextElementSibling === pill, 'render the drop target before the pill')
          const input = unit.querySelector('ui-filled-text-field') as Input
          assert.ok(input, 'renders the path input')
        })

        it('does not allow assertion group onto the action', async () => {
          element.addAction()
          await element.updateComplete

          const chip = element.shadowRoot!.querySelector(
            `ui-chip[data-kind="${CoreAssertions.EqualAssertionKind}"]`
          ) as UiChip
          const target = element.shadowRoot!.querySelector(`.action-row .steps`) as HTMLElement

          await UiMock.dragAndDrop(chip, target)
          await element.updateComplete
          const action = element.model[0].actions[0]
          assert.lengthOf(action.steps, 0, 'has no steps')
        })
      })
    })
  })
})
