import { LitElement, TemplateResult, html, PropertyValues, nothing } from 'lit'
import { property, state } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'
import { statusTemplate } from '../http/HttpStatus.js'
import { RequestLogView } from '../http/RequestLog.js'
import type { ISegmentedButtonSelectionDetail } from '../../md/segmented-button/internals/SegmentedButtonsSet.js'
import type { UiSegmentedButtonElement } from '../../md/segmented-button/ui-segmented-button.js'
import type { IProjectExecutionIteration, IProjectExecutionLog } from '@api-client/core/models/ProjectExecution.js'
import type { IRequestLog } from '@api-client/core/models/RequestLog.js'
import { ErrorResponse } from '@api-client/core/models/ErrorResponse.js'
import type { IResponse } from '@api-client/core/models/Response.js'

import '@github/relative-time-element'
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/segmented-button/ui-segmented-button.js'
import '../../md/segmented-button/ui-segmented-button-set.js'

export enum ProjectRunReportView {
  summary = 'summary',
  details = 'details',
}

/**
 * An element that renders the execution log of the project runner with the IProjectExecutionLog.
 */
export default class ProjectRunReport extends LitElement {
  @property({ type: Object }) accessor report: IProjectExecutionLog | undefined

  /**
   * The index of the selected iteration, if any.
   */
  @state() protected accessor selectedIteration: number | undefined

  /**
   * The index of the selected request, if any
   */
  @state() protected accessor selectedRequest: number | undefined

  /**
   * The currently rendered iteration.
   */
  @state() protected accessor currentIteration: IProjectExecutionIteration | undefined

  @state() protected accessor currentLog: IRequestLog | undefined

  @state() protected accessor view: ProjectRunReportView = ProjectRunReportView.summary

  protected override willUpdate(cp: PropertyValues<this>): void {
    super.updated(cp)
    if (cp.has('report')) {
      this.processReport()
    }
  }

  protected processReport(): void {
    const { report } = this
    if (!report) {
      this.selectedIteration = undefined
      this.selectedRequest = undefined
      this.currentIteration = undefined
      this.currentLog = undefined
      return
    }
    this.selectedIteration = 0
    this.selectedRequest = 0
    const [iteration] = report.iterations
    this.currentIteration = iteration
    this.currentLog = iteration && iteration.executed && iteration.executed[0]
  }

  protected handleIterationSelection(e: CustomEvent): void {
    const element = e.detail.item as HTMLElement
    const { index } = element.dataset
    if (!index) {
      return
    }
    const typed = Number(index)
    if (Number.isNaN(typed)) {
      return
    }
    this.selectIteration(typed)
  }

  selectIteration(index: number): void {
    const { report } = this
    if (!report) {
      return
    }
    const item = report.iterations[index]
    if (!item) {
      return
    }
    this.selectedIteration = index
    this.currentIteration = item
    this.selectedRequest = 0
    this.currentLog = item.executed && item.executed[0]
  }

  protected handleRequestSelection(e: CustomEvent): void {
    const element = e.detail.item as HTMLElement
    const { index } = element.dataset
    if (!index) {
      return
    }
    const typed = Number(index)
    if (Number.isNaN(typed)) {
      return
    }
    this.selectRequest(typed)
  }

  selectRequest(index: number): void {
    const { currentIteration } = this
    if (!currentIteration) {
      return
    }
    const item = currentIteration.executed[index]
    if (!item) {
      return
    }
    this.selectedRequest = index
    this.currentLog = item
  }

  protected handleViewSelection(e: CustomEvent<ISegmentedButtonSelectionDetail>): void {
    if (!e.detail.selected) {
      return
    }
    const button = e.detail.button as UiSegmentedButtonElement
    const view = button.dataset.value as ProjectRunReportView | undefined
    if (view) {
      this.view = view
    }
  }

  protected handleLinkClick(e: Event): void {
    const node = e.target as HTMLElement
    const { iterationIndex, requestIndex, target } = node.dataset
    if (!requestIndex || !iterationIndex) {
      return
    }
    const iteration = Number(iterationIndex)
    const request = Number(requestIndex)
    if (Number.isNaN(request) || Number.isNaN(iteration)) {
      return
    }
    this.selectIteration(iteration)
    this.selectRequest(request)
    this.view = ProjectRunReportView.details
    if (target) {
      this.selectLogView(target as RequestLogView)
    }
  }

  protected handleLinkKeyDown(e: KeyboardEvent): void {
    if (['Enter', 'Space'].includes(e.code)) {
      this.handleLinkClick(e)
    }
  }

  protected async selectLogView(view: RequestLogView): Promise<void> {
    await this.updateComplete
    const node = this.shadowRoot?.querySelector('http-request-log')
    if (!node) {
      return
    }
    node.view = view
  }

  protected override render(): TemplateResult {
    const { report } = this
    if (!report) {
      return html``
    }
    return html` ${this.renderHeader(report)} ${this.renderView()} `
  }

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

  protected renderCurrentSelectors(report: IProjectExecutionLog): TemplateResult | typeof nothing {
    if (this.view === ProjectRunReportView.summary) {
      return nothing
    }
    const hasIterations = report.iterations.length > 1
    return html`
      ${hasIterations ? this.renderInteractionsList(report.iterations) : nothing} ${this.renderRequestsList()}
    `
  }

  protected renderViewToggle(): TemplateResult {
    const { view } = this
    return html`
      <div class="view-toggle">
        <ui-segmented-button-set @select="${this.handleViewSelection}">
          <ui-segmented-button
            .selected="${view === ProjectRunReportView.summary}"
            title="Shows summary of the run report"
            data-value="summary"
            >Summary</ui-segmented-button
          >
          <ui-segmented-button
            .selected="${view === ProjectRunReportView.details}"
            title="Shows detailed response information for each request"
            data-value="details"
            >Details</ui-segmented-button
          >
        </ui-segmented-button-set>
      </div>
    `
  }

  protected renderInteractionsList(iterations: IProjectExecutionIteration[]): TemplateResult {
    const { selectedIteration = 0 } = this
    const iteration = iterations[selectedIteration]
    const index = iteration ? iteration.index + 1 : 1

    return html`
      <ui-dropdown-list
        verticalAlign="top"
        class="iterations-dropdown"
        closeOnOutsideClick
        @select="${this.handleIterationSelection}"
      >
        <ui-button color="tonal">
          <ui-icon slot="icon" icon="arrowDropDown"></ui-icon>
          Iteration #${index}
        </ui-button>
        <ui-list slot="dropdown" role="menu" aria-label="Iterations list">
          ${iterations.map((current) => this.renderIterationOption(current.index))}
        </ui-list>
      </ui-dropdown-list>
    `
  }

  protected renderIterationOption(index: number): TemplateResult {
    const classes = {
      'iteration-option': true,
      'highlight': this.selectedIteration === index,
    }
    return html`
      <ui-list-item role="menuitem" data-index="${index}" class="${classMap(classes)}">
        Iteration #${index + 1}
      </ui-list-item>
    `
  }

  protected renderRequestsList(): TemplateResult {
    const { currentIteration, selectedRequest = 0 } = this
    if (!currentIteration) {
      return this.renderEmptyRequestsList()
    }
    if (!currentIteration.executed || !currentIteration.executed.length) {
      return this.renderEmptyRequestsList()
    }
    const options = currentIteration.executed.map((item, index) => this.renderRequestsOption(item, index))
    const item = currentIteration.executed[selectedRequest]
    const title = (item.request && item.request.url) || `Request #${selectedRequest}`
    return html`
      <ui-dropdown-list
        verticalAlign="top"
        class="history-dropdown"
        closeOnOutsideClick
        @select="${this.handleRequestSelection}"
      >
        <ui-button color="tonal" title="${title}">
          <ui-icon slot="icon" icon="arrowDropDown"></ui-icon>
          <span class="request-title-dropdown">${title}</span>
        </ui-button>
        <ui-list slot="dropdown" role="menu" aria-label="History list"> ${options} </ui-list>
      </ui-dropdown-list>
    `
  }

  protected renderEmptyRequestsList(): TemplateResult {
    return html`
      <ui-button color="tonal" disabled>
        <ui-icon slot="icon" icon="arrowDropDown"></ui-icon>
        No requests
      </ui-button>
    `
  }

  protected renderRequestsOption(item: IRequestLog, index: number): TemplateResult {
    const title = (item.request && item.request.url) || `Request #${index}`
    const error = !item.response || ErrorResponse.isErrorResponse(item.response)
    const classes = {
      'history-option': true,
      error,
      'highlight': this.selectedRequest === index,
    }
    let status = 0
    let statusText: string | undefined
    let duration = 0
    if (!error) {
      const typed = item.response as IResponse
      status = typed.status
      statusText = typed.statusText
      duration = typed.loadingTime
    }
    return html`
      <ui-list-item lines="two" role="menuitem" data-index="${index}" class="${classMap(classes)}">
        <div class="history-label">
          <span class="label-value">${statusTemplate(status, statusText)} ${duration} ms</span>
        </div>
        <span slot="supporting-text">${title}</span>
      </ui-list-item>
    `
  }

  protected renderView(): TemplateResult {
    switch (this.view) {
      case ProjectRunReportView.details:
        return this.renderLog()
      case ProjectRunReportView.summary:
        return this.renderSummary()
      default:
        return html`<p>Invalid view selection.</p>`
    }
  }

  protected renderLog(): TemplateResult {
    const { currentLog } = this
    return html`
      <div class="response">
        ${currentLog ? html`<http-request-log .httpLog="${currentLog}"></http-request-log>` : ''}
      </div>
    `
  }

  protected renderSummary(): TemplateResult {
    const { report } = this
    if (!report) {
      return html`<p>Report is not set.</p>`
    }
    const { iterations, ended } = report
    const de = new Date(ended)
    return html`
      <div class="summary-header">
        Run ended <relative-time slot="supporting-text" datetime="${de.toISOString()}"></relative-time>
      </div>
      ${iterations.map((iteration) => this.renderIterationSummary(iteration))}
    `
  }

  protected renderIterationSummary(iteration: IProjectExecutionIteration): TemplateResult {
    const { error, executed, index } = iteration
    return html` ${error ? this.renderIterationError() : this.renderIterationSummaryTable(index, executed)} `
  }

  protected renderIterationError(): TemplateResult {
    return html` <p class="iteration-error">The iteration was not executed due to an error.</p> `
  }

  protected renderIterationSummaryTable(iterationIndex: number, executed: IRequestLog[]): TemplateResult {
    const failedIndex: string[] = []
    executed.forEach((item, index) => {
      const { assertions = [] } = item
      const hasFailed = assertions.find((i) => !i.ok)
      if (hasFailed) {
        failedIndex.push(`#${index + 1}`)
      }
    })
    const formatter = new Intl.ListFormat(undefined, { style: 'long', type: 'conjunction' })
    return html`
      <table class="execution-table">
        <caption>
          Iteration #${iterationIndex + 1}. Failed requests:
          ${failedIndex.length ? formatter.format(failedIndex) : 'none'}.
        </caption>
        <thead>
          <tr>
            <th scope="col" class="request">HTTP request</th>
            <th scope="col" class="status">Status</th>
            <th scope="col" class="time">Time</th>
            <th scope="col" class="assertions">Assertions</th>
          </tr>
        </thead>
        <tbody>
          ${executed.map((item, requestIndex) => this.renderSummaryTableRow(item, iterationIndex, requestIndex))}
        </tbody>
      </table>
    `
  }

  renderSummaryTableRow(item: IRequestLog, iterationIndex: number, requestIndex: number): TemplateResult {
    const { assertions = [], request, response } = item
    if (!request) {
      return html`
        <tr>
          <td colspan="3">The request was not executed (missing request info)</td>
        </tr>
      `
    }
    const error = !item.response || ErrorResponse.isErrorResponse(item.response)
    let status: TemplateResult
    let duration = 0
    if (error) {
      status = html`Errored`
    } else if (response) {
      status = statusTemplate(response.status, response.statusText)
      duration = (response as IResponse).loadingTime
    } else {
      status = html`Unknown`
    }
    const successAssertions = (item.assertions || []).filter((i) => i.ok)
    const hasFailed = assertions.length - successAssertions.length !== 0
    return html`
      <tr>
        <td class="request">
          <span class="http-label pill" data-method="${(request.method || 'get').toLowerCase()}"
            >${request.method}</span
          >
          <span
            class="link"
            title="Open details"
            data-iteration-index="${iterationIndex}"
            data-request-index="${requestIndex}"
            @click="${this.handleLinkClick}"
            @keydown="${this.handleLinkKeyDown}"
            tabindex="0"
            >${request.url}</span
          >
        </td>
        <td class="status">${status}</td>
        <td class="time">${duration}ms</td>
        <td class="assertions">
          <span
            class="link"
            title="Open assertion details"
            data-iteration-index="${iterationIndex}"
            data-request-index="${requestIndex}"
            data-target="${RequestLogView.assertions}"
            @click="${this.handleLinkClick}"
            @keydown="${this.handleLinkKeyDown}"
            tabindex="0"
            >${successAssertions.length}/${assertions.length}</span
          >
          ${hasFailed ? html`<div class="badge small"></div>` : ''}
        </td>
      </tr>
    `
  }
}
