import { html, type TemplateResult, type PropertyValues, nothing } from 'lit'
import { property, state } from 'lit/decorators.js'
import { ApiEditor } from './ApiEditor.js'
import type { ApiModel, RateLimitRule, RateLimitingConfiguration } from '@api-client/core/browser.js'
import { isValidAccessTarget, isValidOperationName } from '../lib/validators.js'
import type { ApiModelAccessTarget, ApiModelOperationName } from '../types.js'
import { renderHelp } from '../../help/render.js'

/**
 * An editor for configuring rate limiting settings at the API, endpoint, or action level.
 *
 * This editor writes directly into the provided model (no separate dirty buffer).
 */
export default class ApiModelingRateLimiting extends ApiEditor {
  /**
   * The operation name when editing action-level limits.
   * Ignored when target is 'api' or 'endpoint'.
   * @attribute
   */
  @property({ type: String }) accessor operation: ApiModelOperationName | undefined

  /**
   * The target scope where limits are applied: api | endpoint | action.
   * @attribute
   */
  @property({ type: String }) accessor target: ApiModelAccessTarget | undefined

  /**
   * The editable list of rules. This is a direct reference when available.
   */
  @state() accessor rules: RateLimitRule[] = []

  constructor() {
    super()
    this.operation = undefined
    this.target = undefined
  }

  protected override willUpdate(cp: PropertyValues<this>): void {
    if (cp.has('operation') || cp.has('target')) {
      this.setupModel(this.model, this.domainKey)
    } else {
      super.willUpdate(cp)
    }
  }

  protected resetModel(): void {
    this.rules = []
  }

  protected override setupModel(model?: ApiModel, domainKey?: string): void | Promise<void> {
    this.resetModel()
    if (!model || !isValidAccessTarget(this.target)) {
      return
    }
    const rules = this.readRules(model, this.target, domainKey, this.operation)
    this.rules = rules ?? []
  }

  /**
   * Reads rules from the model for the given scope.
   */
  protected readRules(
    model: ApiModel,
    target: ApiModelAccessTarget,
    domainKey?: string,
    operation?: ApiModelOperationName
  ): RateLimitRule[] | undefined {
    if (target === 'api') {
      return model.rateLimiting?.rules
    }
    if (!domainKey) {
      return undefined
    }
    const entity = model.exposes.find((e) => e.key === domainKey)
    if (!entity) {
      return undefined
    }
    if (target === 'endpoint') {
      return entity.rateLimiting?.rules
    }
    if (!isValidOperationName(operation)) {
      return undefined
    }
    const action = entity.actions.find((a) => a.kind === operation)
    return action?.rateLimiting?.rules
  }

  /**
   * Synchronizes the current rules array into the model at the proper scope.
   */
  protected synchronizeModel(): void {
    const { model, target, operation } = this
    if (!model || !isValidAccessTarget(target)) {
      return
    }
    const src = this.rules ?? []
    if (target === 'api') {
      model.rateLimiting = src.length > 0 ? ({ rules: src } satisfies RateLimitingConfiguration) : undefined
      return
    }
    const { domainKey } = this
    if (!domainKey) {
      return
    }
    const entity = model.exposes.find((e) => e.key === domainKey)
    if (!entity) {
      return
    }
    if (target === 'endpoint') {
      entity.rateLimiting = src.length > 0 ? ({ rules: src } satisfies RateLimitingConfiguration) : undefined
      return
    }
    if (!isValidOperationName(operation)) {
      return
    }
    const action = entity.actions.find((a) => a.kind === operation)
    if (!action) {
      return
    }
    action.rateLimiting = src.length > 0 ? ({ rules: src } satisfies RateLimitingConfiguration) : undefined
  }

  /**
   * Adds a new rule with sensible defaults.
   */
  addRule(): void {
    const rule: RateLimitRule = {
      description: '',
      key: { type: 'ip' },
      rate: 60,
      interval: 'minute',
      burst: 60,
    }
    this.rules.push(rule)
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  deleteRule(index: number): void {
    if (index < 0 || index >= this.rules.length) {
      return
    }
    this.rules.splice(index, 1)
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleAddRuleClick(): void {
    this.addRule()
  }

  protected handleDeleteRuleClick(e: Event): void {
    const btn = e.currentTarget as HTMLElement
    const index = Number(btn.dataset.index)
    if (!Number.isNaN(index)) {
      this.deleteRule(index)
    }
  }

  protected handleDescriptionChange(e: Event): void {
    const input = e.currentTarget as HTMLInputElement
    const index = Number(input.dataset.index)
    if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
      return
    }
    this.rules[index].description = input.value
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleKeyTypeChange(e: CustomEvent<{ value: string }>): void {
    const select = e.currentTarget as HTMLElement
    const index = Number(select.dataset.index)
    if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
      return
    }
    const v = e.detail.value
    if (v !== 'ip' && v !== 'userId' && v !== 'role') {
      return
    }
    const rule = this.rules[index]
    if (v === 'role') {
      rule.key = { type: 'role', value: '' }
    } else {
      rule.key = { type: v }
    }
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleRoleValueChange(e: Event): void {
    const input = e.currentTarget as HTMLInputElement
    const index = Number(input.dataset.index)
    if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
      return
    }
    const rule = this.rules[index]
    if (rule.key.type !== 'role') {
      return
    }
    rule.key.value = input.value
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleRateChange(e: Event): void {
    const input = e.currentTarget as HTMLInputElement
    const index = Number(input.dataset.index)
    if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
      return
    }
    const value = input.valueAsNumber
    if (!Number.isFinite(value) || value <= 0) {
      return
    }
    this.rules[index].rate = value
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleIntervalChange(e: CustomEvent<{ value: string }>): void {
    const select = e.currentTarget as HTMLElement
    const index = Number(select.dataset.index)
    if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
      return
    }
    const v = e.detail.value
    if (v !== 'second' && v !== 'minute' && v !== 'hour' && v !== 'day') {
      return
    }
    this.rules[index].interval = v
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  protected handleBurstChange(e: Event): void {
    const input = e.currentTarget as HTMLInputElement
    const index = Number(input.dataset.index)
    if (Number.isNaN(index) || index < 0 || index >= this.rules.length) {
      return
    }
    const value = input.valueAsNumber
    if (!Number.isFinite(value) || value <= 0) {
      return
    }
    this.rules[index].burst = value
    this.synchronizeModel()
    this.notifyChange()
    this.requestUpdate()
  }

  protected override render(): TemplateResult | typeof nothing {
    if (!isValidAccessTarget(this.target)) {
      return nothing
    }
    return html`
      <div class="intro">
        <div class="header">
          <span class="title-large">Rate limiting</span>
          ${renderHelp('modeling', 'api:rate-limiting')}
        </div>
        <span class="inline-help body-medium">
          Define throttling rules to protect the API or specific endpoints/actions from overuse.
        </span>
      </div>

      <ui-button color="outlined" @click="${this.handleAddRuleClick}">
        <ui-icon slot="icon" icon="add" aria-hidden="true"></ui-icon>
        Add rule
      </ui-button>

      ${this.renderRules()}
    `
  }

  protected renderRules(): TemplateResult | typeof nothing {
    const { rules } = this
    if (!rules || rules.length === 0) {
      return nothing
    }
    const items: TemplateResult[] = []
    const len = rules.length
    rules.forEach((rule, index) => {
      items.push(this.renderRule(rule, index))
      if (index < len - 1) {
        items.push(html`<ui-divider class="rule-divider"></ui-divider>`) // separator
      }
    })
    return html`<div class="rules">${items}</div>`
  }

  protected renderRule(rule: RateLimitRule, index: number): TemplateResult {
    return html`
      <div class="rule">
        <div class="rule-header">
          <span class="rule-title title-medium">Rule #${index + 1}</span>
          <div class="actions">
            ${renderHelp('modeling', 'api:rate-limiting:rule')}
            <ui-icon-button type="button" data-index="${index}" @click="${this.handleDeleteRuleClick}">
              <ui-icon>delete_outline</ui-icon>
            </ui-icon-button>
          </div>
        </div>

        <div class="rule-body">
          <div class="row input-row">
            <ui-outlined-text-field
              class="input"
              label="Description (optional)"
              .value=${rule.description ?? ''}
              data-index="${index}"
              @change=${this.handleDescriptionChange}
            ></ui-outlined-text-field>
          </div>

          <div class="row input-row">
            <div class="input select">
              <ui-select
                class="form-field"
                .value="${rule.key.type}"
                data-index="${index}"
                label="Limit target"
                required
                @change=${this.handleKeyTypeChange}
              >
                <ui-option value="ip">Client IP</ui-option>
                <ui-option value="userId">User ID</ui-option>
                <ui-option value="role">User role</ui-option>
              </ui-select>
            </div>
            ${rule.key.type === 'role'
              ? html`<ui-outlined-text-field
                  class="input"
                  label="Role value"
                  .value=${'value' in rule.key ? rule.key.value : ''}
                  data-index="${index}"
                  @change=${this.handleRoleValueChange}
                ></ui-outlined-text-field>`
              : nothing}
          </div>

          <div class="row input-row">
            <ui-outlined-text-field
              class="input"
              type="number"
              label="Rate"
              min="1"
              required
              .value=${String(rule.rate)}
              data-index="${index}"
              @change=${this.handleRateChange}
            ></ui-outlined-text-field>
            <ui-select
              class="input"
              .value="${rule.interval}"
              data-index="${index}"
              label="Interval"
              required
              @change=${this.handleIntervalChange}
            >
              <ui-option value="second">Second</ui-option>
              <ui-option value="minute">Minute</ui-option>
              <ui-option value="hour">Hour</ui-option>
              <ui-option value="day">Day</ui-option>
            </ui-select>
            <ui-outlined-text-field
              class="input"
              type="number"
              label="Burst"
              min="1"
              required
              .value=${String(rule.burst)}
              data-index="${index}"
              @change=${this.handleBurstChange}
            ></ui-outlined-text-field>
          </div>
        </div>
      </div>
    `
  }
}
