import type { IHttpHistory } from '@api-client/core/models/HttpHistory.js'
import type { IRequestLog, RequestLog } from '@api-client/core/models/RequestLog.js'
import type { IHistoryRequestListOptions } from '@api-client/core/events/BaseEvents.js'
import type { IResponse } from '@api-client/core/models/Response.js'
import { ErrorResponse } from '@api-client/core/models/ErrorResponse.js'
import { TemplateResult, html, PropertyValues, nothing } from 'lit'
import { property, state } from 'lit/decorators.js'
import ApiElement from '../ApiElement.js'
import { relativeDay } from '../../lib/time/Conversion.js'
import { HttpHistoryChart } from './HttpHistoryChart.js'
import { statusTemplate } from '../http/HttpStatus.js'
// import { Events } from '../../events/Events.js'
import type SegmentedButton from '../../md/segmented-button/internals/SegmentedButton.js'
import UiDialog, { UiDialogClosingReason } from '../../md/dialog/internals/Dialog.js'
import '@github/relative-time-element'
// import { SnackNotifications } from '../../md/notification/SnackNotifications.js'
import type UiIconButton from '../../md/icon-button/internals/IconButton.js'
import '../../define/http/http-request-log.js'
import '../../md/icons/ui-icon.js'
import '../../md/button/ui-button.js'
import '../../md/dropdown-list/ui-dropdown-list.js'
import '../../md/list/ui-list.js'
import '../../md/list/ui-list-item.js'
import '../../md/divider/ui-divider.js'
import '../../md/segmented-button/ui-segmented-button.js'
import '../../md/segmented-button/ui-segmented-button-set.js'
import '../../md/dialog/ui-dialog.js'

export enum ProjectHistoryView {
  history = 'history',
  analysis = 'analysis',
}

/**
 * An element that renders HTTP history for a configured list of requests.
 * This element is just a view and contains no logic related to store communication
 * or user interactions. Use the dispatched events to handle user interactions.
 */
export default class HttpProjectRequestHistory extends ApiElement {
  /**
   * The history list to render.
   */
  @state() accessor history: IHttpHistory[] = []

  /**
   * The key of the project the request belongs to.
   */
  @property({ type: String }) accessor project: string | undefined

  /**
   * The key of the request to render the history for.
   * Both `request` and `project` properties must be set to render any view.
   */
  @property({ type: String }) accessor request: string | undefined

  /**
   * Returns true when the element is correctly setup.
   */
  get isSetup(): boolean {
    return !!this.project && !!this.request
  }

  /**
   * Whether querying for a page of results.
   */
  @state() accessor querying = false

  /**
   * A sorted list of history items by the `midnight` property.
   */
  @state() protected accessor model: IHttpHistory[][] | undefined

  /**
   * The key of the selected request, if any
   */
  @state() accessor selectedItem: string | undefined

  /**
   * The currently rendered item.
   */
  @state() protected accessor renderedItem: IHttpHistory | IRequestLog | RequestLog | undefined

  @state() protected accessor view: ProjectHistoryView = ProjectHistoryView.history

  protected debounceTimeout?: number

  /**
   * The cursor returned by the store used in pagination.
   */
  protected pageCursor?: string

  protected override willUpdate(cp: PropertyValues<this>): void {
    if (cp.has('project') || cp.has('request')) {
      this.reset()
      this.queryDebounce()
    }
    if (cp.has('selectedItem')) {
      this.computeSelectedItem()
    }
    super.willUpdate(cp)
  }

  // protected handleBroadcast(e: MessageEvent): void {
  //   const event = e.data as BroadcastEvent
  //   switch (event.operation) {
  //     case 'created':
  //       this.handleHistoryCreated(event as HistoryCreatedBroadcastEvent)
  //       break
  //     case 'deleted':
  //       this.handleHistoryDeleted(event as HistoryDeletedBroadcastEvent)
  //       break
  //     default:
  //   }
  // }

  // protected handleHistoryCreated(event: HistoryCreatedBroadcastEvent): void {
  //   const { request, data } = event
  //   const item = data as IHttpHistory
  //   if (this.request !== request) {
  //     return
  //   }
  //   if (item) {
  //     this.addItem(item, true)
  //   }
  // }

  // protected handleHistoryDeleted(event: HistoryDeletedBroadcastEvent): void {
  //   const { model, history } = this
  //   const { key, request } = event
  //   if (!model || !history || request !== this.request) {
  //     return
  //   }
  //   const historyIndex = history.findIndex((i) => i.key === key)
  //   if (historyIndex < 0) {
  //     return
  //   }
  //   history.splice(historyIndex, 1)
  //   for (const group of model) {
  //     const index = group.findIndex((i) => i.key === key)
  //     if (index >= 0) {
  //       group.splice(index, 1)
  //       if (group.length === 0) {
  //         const groupIndex = model.indexOf(group)
  //         model.splice(groupIndex, 1)
  //       }
  //       this.requestUpdate()
  //       this.setupVisibility()
  //       break
  //     }
  //   }
  // }

  protected setupVisibility(): void {
    if (this.history && !this.history.length) {
      this.setAttribute('hidden', '')
    } else {
      this.removeAttribute('hidden')
    }
  }

  reset(): void {
    this.history = []
    this.pageCursor = undefined
    this.model = undefined
    this.selectedItem = undefined
    this.setupVisibility()
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
      this.debounceTimeout = undefined
    }
  }

  queryDebounce(): void {
    if (!this.isSetup) {
      return
    }
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout)
    }
    this.debounceTimeout = setTimeout(() => {
      delete this.debounceTimeout
      this.queryPage()
    }, 1) as unknown as number
  }

  protected async queryPage(): Promise<void> {
    const { project, request, pageCursor } = this
    if (!project || !request || this.querying) {
      // todo: set `dirty` flag and re-query after finish.
      return
    }
    this.querying = true
    const init: IHistoryRequestListOptions = {
      type: 'request',
      project,
      id: request,
      organization: '', // this is filled by the bindings
    }
    if (pageCursor) {
      init.cursor = pageCursor
    }
    try {
      throw new Error(`Not implemented yet.`)
      // const result = await Events.Store.History.list(init, this)
      // if (!result) {
      //   return
      // }
      // const { items, cursor } = result
      // if (items) {
      //   this.addItems(items)
      // }
      // if (cursor) {
      //   this.pageCursor = cursor
      // }
    } finally {
      this.querying = false
      this.setupVisibility()
    }
  }

  addItems(items: IHttpHistory[]): void {
    const { history = [] } = this
    this.history = history.concat(items)
    const all = history.concat(items)
    const sorted = this.sortHistory(all)
    this.model = sorted
    this.computeSelectedItem()
    if (sorted.length && !this.renderedItem) {
      this.selectedItem = sorted[0][0].key
    }
    this.setupVisibility()
  }

  /**
   * Adds a new history to the list.
   * This is used instead of re-assigning the entire array and triggering initial computations.
   * @param item The history item to add
   * @param select When true it makes this item selected,
   */
  addItem(item: IHttpHistory, select?: boolean): void {
    const { model } = this
    this.history.push(item)
    if (!model || !model.length) {
      this.model = [[item]]
      this.selectedItem = item.key
      this.requestUpdate()
      this.setupVisibility()
      return
    }
    const { midnight = Date.now() } = item
    const index = model.findIndex((i) => i[0].midnight === midnight)
    if (index >= 0) {
      this.pushSorted(model[index], item)
    } else {
      const position = model.findIndex((i) => ((i[0] && i[0].midnight) || 0) < midnight)
      model.splice(position, 0, [item])
    }
    if (select && item.key) {
      this.selectedItem = item.key
    }
    this.requestUpdate()
    this.setupVisibility()
  }

  protected sortHistory(history: IHttpHistory[]): IHttpHistory[][] {
    if (!Array.isArray(history) || !history.length) {
      return []
    }
    const result: IHttpHistory[][] = []
    const now = Date.now()
    history.forEach((current) => {
      const { midnight = now } = current
      const index = result.findIndex((i) => i[0].midnight === midnight)
      if (index >= 0) {
        this.pushSorted(result[index], current)
      } else if (result[0] && result[0][0] && result[0][0].midnight && result[0][0].midnight < midnight) {
        result.unshift([current])
      } else {
        result.push([current])
      }
    })
    return result
  }

  protected computeSelectedItem(): void {
    const { history, selectedItem } = this
    this.renderedItem = undefined
    if (!selectedItem || !Array.isArray(history)) {
      return
    }
    this.renderedItem = history.find((i) => i.key === selectedItem)
  }

  /**
   * Allows to push a history item into a right index depending on the `created` property
   * so the data processor can skip sorting each group after mutation.
   *
   * The item is inserted before an item that is older (has lower `created` property).
   *
   * @param list The list to insert the item to
   * @param item The item to insert.
   */
  protected pushSorted(list: IHttpHistory[], item: IHttpHistory): void {
    const older = list.findIndex((i) => i.created < item.created)
    if (older >= 0) {
      list.splice(older, 0, item)
    } else {
      // this item is the oldest
      list.push(item)
    }
  }

  protected handleSelection(e: CustomEvent): void {
    const item = e.detail.item as HTMLElement | undefined
    if (!item) {
      return
    }
    const { key } = item.dataset
    if (!key) {
      return
    }
    this.selectedItem = key
  }

  protected handleViewSelection(e: CustomEvent): void {
    if (!e.detail.selected) {
      return
    }
    const button = e.detail.button as SegmentedButton
    const view = button.dataset.value as ProjectHistoryView | undefined
    if (view) {
      this.view = view
    }
  }

  protected handleHistoryOption(e: CustomEvent): void {
    const element = e.detail.item as HTMLElement
    const { option } = element.dataset
    if (!option) {
      return
    }
    if (option === 'clear') {
      this.triggerClearHistoryDialog()
    }
  }

  protected triggerClearHistoryDialog(): void {
    const dialog = this.shadowRoot?.querySelector('.clear-dialog') as UiDialog | null
    if (!dialog) {
      return
    }
    dialog.open = true
  }

  protected handleClearHistoryDialogResult(e: CustomEvent<UiDialogClosingReason>): void {
    if (e.detail.cancelled) {
      return
    }
    this.clearHistory()
  }

  async clearHistory(): Promise<void> {
    const { project, request } = this
    if (!project || !request) {
      return
    }
    // space parameter is filled by bindings.
    throw new Error(`Not implemented yet.`)
    // await Events.Store.History.clearRequest('', project, request, this)
    // SnackNotifications.notify('History cleared')
    // this.reset()
  }

  protected handleItemDelete(e: Event): void {
    const button = e.currentTarget as UiIconButton
    const { key } = button.dataset
    if (!key) {
      return
    }
    this.deleteHistoryItem(key)
  }

  async deleteHistoryItem(key: string): Promise<void> {
    if (!key) {
      return
    }
    const item = this.history.find((i) => i.key === key)
    if (!item) {
      return
    }
    throw new Error(`Not implemented yet.`)
    // await Events.Store.History.delete(key, this.request, this)
    // SnackNotifications.notify('History item deleted', {
    //   actionLabel: 'Undo',
    //   actionCallback: () => {
    //     this.revertHistoryItemDelete(item)
    //   },
    //   timeout: 7000,
    // })
  }

  async revertHistoryItemDelete(item: IHttpHistory): Promise<void> {
    throw new Error(`Not implemented yet. ${item}`)
    // const info = SnackNotifications.notify('Reverting...', { persistent: true })
    // try {
    //   await Events.Store.History.create(item, this)
    //   SnackNotifications.cancel(info)
    //   SnackNotifications.notify('Reverted history item.')
    // } catch (e) {
    //   SnackNotifications.cancel(info)
    //   SnackNotifications.notify('Unable to revert changes.')
    // }
  }

  protected override render(): TemplateResult | typeof nothing {
    const { model } = this
    if (!model || !model.length) {
      if (this.querying) {
        return this.renderLoader()
      }
      return nothing
    }
    return html` ${this.renderHeader()} ${this.renderView()} ${this.renderClearHistoryDialog()} `
  }

  protected renderLoader(): TemplateResult {
    return html`<progress></progress>`
  }

  protected renderHeader(): TemplateResult {
    return html`
      <div class="header">
        ${this.renderHistoryDropdown()} ${this.renderHistoryOptions()} ${this.renderViewToggle()}
      </div>
    `
  }

  protected renderHistoryDropdown(): TemplateResult {
    const { model } = this
    const disabled = !model || !model.length
    return html`
      <ui-dropdown-list
        verticalAlign="top"
        class="history-dropdown"
        closeOnOutsideClick
        @select="${this.handleSelection}"
      >
        <ui-button color="tonal" ?disabled="${disabled}">
          <ui-icon slot="icon" icon="arrowDropDown"></ui-icon>
          History
        </ui-button>
        <ui-list slot="dropdown" role="menu" aria-label="History list"> ${this.renderHistoryList()} </ui-list>
      </ui-dropdown-list>
    `
  }

  protected renderHistoryOptions(): TemplateResult {
    return html`
      <ui-dropdown-list @select="${this.handleHistoryOption}" closeOnOutsideClick class="dropdown">
        <ui-icon-button aria-label="Additional options"
          ><ui-icon icon="moreVert" role="presentation"></ui-icon
        ></ui-icon-button>
        <ui-list slot="dropdown" role="menu">
          <ui-list-item role="menuitem" data-option="clear" image="icon" title="Deletes all history for the request">
            <ui-icon role="presentation" slot="start" icon="deleteOutline"></ui-icon>
            Clear history
          </ui-list-item>
        </ui-list>
      </ui-dropdown-list>
    `
  }

  protected renderHistoryList(): (TemplateResult | typeof nothing)[] | typeof nothing {
    const { model } = this
    if (!Array.isArray(model) || !model.length) {
      return nothing
    }
    return model.map((group) => this.renderHistoryGroup(group))
  }

  protected renderHistoryGroup(group: IHttpHistory[]): TemplateResult | typeof nothing {
    if (!group.length) {
      // this should not happen as the model is computed internally,
      // but I prefer to do just one check to not stop execution when error.
      return nothing
    }
    const [first] = group
    return html` ${this.renderHistoryGroupTitle(first)} ${group.map((item) => this.renderHistoryItem(item))} `
  }

  protected renderHistoryGroupTitle(item: IHttpHistory): TemplateResult {
    const { midnight = 0 } = item
    return html`
      <div class="history-group-title">${relativeDay(midnight)}</div>
      <ui-divider type="inset"></ui-divider>
    `
  }

  protected renderHistoryItem(item: IHttpHistory): TemplateResult | typeof nothing {
    const d = new Date(item.created)
    const isoTime = d.toISOString()

    const { log } = item
    const { request, response } = log
    const method = request && request.method
    const url = request && request.url
    if (!method || !url) {
      return nothing
    }
    const error = !response || ErrorResponse.isErrorResponse(response)

    let status = 0
    let statusText: string | undefined
    let duration = 0
    if (!error) {
      const typed = response as IResponse
      status = typed.status
      statusText = typed.statusText
      duration = typed.loadingTime
    }
    return html`
      <ui-list-item lines="two" role="menuitem" data-key="${item.key as string}" data-kind="${item.kind}">
        <div class="history-label">
          <span class="label-value">${statusTemplate(status, statusText)} ${duration} ms</span>
          ${this.renderHistoryItemDelete(item)}
        </div>
        <relative-time slot="supporting-text" datetime="${isoTime}"></relative-time>
      </ui-list-item>
    `
  }

  protected renderHistoryItemDelete(item: IHttpHistory): TemplateResult | typeof nothing {
    return html`
      <ui-icon-button
        class="delete"
        aria-label="Delete request"
        data-key="${item.key as string}"
        data-kind="${item.kind}"
        @click="${this.handleItemDelete}"
        ><ui-icon icon="deleteOutline" role="presentation"></ui-icon
      ></ui-icon-button>
    `
  }

  protected renderView(): TemplateResult | string {
    const { view } = this
    if (view === ProjectHistoryView.analysis) {
      return this.renderAnalysis()
    }
    return this.renderLog()
  }

  protected renderLog(): TemplateResult | string {
    const { renderedItem } = this
    if (!renderedItem) {
      return ''
    }
    const typed = renderedItem as IHttpHistory
    const data = typed.log ? typed.log : (renderedItem as IRequestLog | RequestLog)
    return html` <http-request-log .httpLog="${data}"></http-request-log> `
  }

  protected renderAnalysis(): TemplateResult | string {
    const { model } = this
    if (!model) {
      return ''
    }
    const chart = new HttpHistoryChart(model)
    return chart.durationPlot()
  }

  protected renderViewToggle(): TemplateResult {
    return html`
      <div class="view-toggle">
        <ui-segmented-button-set @select="${this.handleViewSelection}">
          <ui-segmented-button selected title="Shows response history" data-value="history"
            >History</ui-segmented-button
          >
          <ui-segmented-button title="Shows history analysis" data-value="analysis">Analysis</ui-segmented-button>
        </ui-segmented-button-set>
      </div>
    `
  }

  protected renderClearHistoryDialog(): TemplateResult {
    return html`
      <ui-dialog modal class="clear-dialog" @close="${this.handleClearHistoryDialogResult}">
        <ui-icon slot="icon" icon="deleteOutline"></ui-icon>
        <span slot="title">Delete history?</span>
        <p>This will clear responses history for this request.</p>
        <ui-button color="text" slot="button" value="dismiss">Cancel</ui-button>
        <ui-button color="text" slot="button" value="confirm">Accept</ui-button>
      </ui-dialog>
    `
  }
}
