import { html, type TemplateResult, nothing } from 'lit'
import { DomainEntity, SemanticType, type ApiModel, type DomainProperty } from '@api-client/core/browser.js'
import { live } from 'lit/directives/live.js'
import { state } from 'lit/decorators.js'
import { ApiModelingScreen } from './ApiModelingScreen.js'
import { renderHelp } from '../../help/render.js'
import type { UiSelectElement } from '../../md/select/ui-select.js'
import type {
  AuthenticationConfiguration,
  AuthorizationConfiguration,
  RolesBasedAccessControl,
  UsernamePasswordConfiguration,
} from '@api-client/core/modeling/types.js'

/**
 * A screen for editing API access information.
 * Used in API modeling.
 *
 * Note that unlike configuration components, screens alter the model directly
 * and notify changes as needed (on the model).
 */
export default class ApiModelingAccessScreen extends ApiModelingScreen {
  /**
   * A computed property from the data domain model that has the password semantic tag.
   */
  @state() protected accessor passwordProperty: DomainProperty | null = null
  /**
   * A computed property from the data domain model that has the user role semantic tag.
   *
   * TODO:
   * - We should scan the entire data domain for UserRole semantic as likely there might be more than one.
   *   We should recommend the one on the `User` entity if multiple found.
   */
  @state() protected accessor roleProperty: DomainProperty | null = null

  protected handleModelChange(model: ApiModel | undefined): void {
    this.passwordProperty = null
    this.roleProperty = null
    if (!model) {
      return
    }
    const domain = model.domain
    if (!domain) {
      return
    }
    // check local entities
    for (const entity of domain.listEntities()) {
      if (entity.hasSemantic(SemanticType.User)) {
        this.analyzeUserEntity(entity)
        return
      }
    }
    // check foreign entities
    for (const entity of domain.listAllForeignEntities()) {
      if (entity.hasSemantic(SemanticType.User)) {
        this.analyzeUserEntity(entity)
        return
      }
    }
  }

  protected analyzeUserEntity(entity: DomainEntity): void {
    let passwordProp: DomainProperty | null = null
    let roleProp: DomainProperty | null = null
    for (const property of entity.listProperties()) {
      if (property.hasSemantic(SemanticType.Password)) {
        passwordProp = property
      }
      if (property.hasSemantic(SemanticType.UserRole)) {
        roleProp = property
      }
      if (passwordProp && roleProp) {
        break
      }
    }
    this.passwordProperty = passwordProp
    this.roleProperty = roleProp
  }

  protected handleAuthStrategyChange(e: CustomEvent): void {
    const model = this.model
    if (!model) {
      return
    }
    const select = e.target as UiSelectElement
    const { value } = select

    if (model.authentication?.strategy === value) {
      // Ignore resetting the same value.
      return
    }

    if (value === 'UsernamePassword') {
      if (!model.authentication || model.authentication.strategy !== 'UsernamePassword') {
        model.authentication = {
          strategy: 'UsernamePassword',
        } as UsernamePasswordConfiguration
      }
    } else {
      // For now, only UsernamePassword is a valid, non-disabled option.
      // If other options are added, this logic will need to be expanded.
      if (!model.authentication) {
        model.authentication = { strategy: value } as AuthenticationConfiguration
      } else {
        model.authentication.strategy = value as string
      }
    }
    this.model?.notifyChange()
    this.requestUpdate()
  }

  protected handleAuthzStrategyChange(e: CustomEvent): void {
    const model = this.model
    if (!model) {
      return
    }
    const select = e.target as UiSelectElement
    const { value } = select

    if (model.authorization?.strategy === value) {
      // Ignore resetting the same value.
      return
    }

    if (value === 'RBAC') {
      if (!model.authorization || model.authorization.strategy !== 'RBAC') {
        model.authorization = {
          strategy: 'RBAC',
          roleKey: this.roleProperty?.key || '', // default value
        } as RolesBasedAccessControl
      }
    } else {
      // For now, only RBAC is a valid, non-disabled option.
      if (!model.authorization) {
        model.authorization = { strategy: value } as AuthorizationConfiguration
      } else {
        model.authorization.strategy = value as string
      }
    }
    this.model?.notifyChange()
    this.requestUpdate()
  }

  protected handleAccessConfigChange(): void {
    this.model?.notifyChange()
  }

  override render(): TemplateResult | typeof nothing {
    const model = this.model
    if (!model) {
      return nothing
    }
    return html`
      <div class="screen-container">
        <div class="screen-header">
          <h2 class="headline-medium">API Access</h2>
          <span class="body-medium description">
            API access allows you to control how your API customers access the API. Here, you can configure the
            authentication strategy and global access rules. You can update these settings for each behaviors
            separately.
          </span>
        </div>
        ${this.renderAuthentication(model)} ${this.renderAuthorization(model)} ${this.renderApiAccessConfiguration()}
      </div>
    `
  }

  protected renderAuthentication(model: ApiModel): TemplateResult {
    const authStrategy = model.authentication?.strategy
    return html`
      <div class="config-section authentication">
        <div class="section-header">
          <h3 class="title-large">Authentication strategy</h3>
          <span class="body-small description">
            Choose how your API will verify the identity of its users. You can change these settings until you first
            deploy your API. After that the change won't be possible.
          </span>
        </div>
        <div class="row">
          <ui-select
            label="Strategy"
            name="authenticationStrategy"
            .value="${live(authStrategy)}"
            @change="${this.handleAuthStrategyChange}"
          >
            <ui-option value="UsernamePassword">
              <div>Username and Password</div>
              <div slot="supporting-text">Authenticate with a username and password.</div>
            </ui-option>
            <ui-option value="SSO" disabled>
              <div>Single Sign-On</div>
              <div slot="supporting-text">Coming soon</div>
            </ui-option>
          </ui-select>
          ${renderHelp('api', 'auth-strategy-select')}
        </div>

        ${this.renderPasswordConfirmation(model)}
      </div>
    `
  }

  protected renderPasswordConfirmation(model: ApiModel): TemplateResult | typeof nothing {
    const strategy = model.authentication?.strategy
    if (strategy !== 'UsernamePassword') {
      return nothing
    }
    const prop = this.passwordProperty
    if (!prop) {
      return html`<span class="body-small no-property-warning">
        <ui-icon>warning_amber</ui-icon>
        No password property found on the user entity. Please define one on the data domain's User entity to ensure
        secure authentication.
      </span>`
    }
    return html`<span class="body-small property-info"
      >Password property: <strong>${prop.info.getLabel()}</strong></span
    >`
  }

  protected renderAuthorization(model: ApiModel): TemplateResult {
    const authzStrategy = model.authorization?.strategy || ''
    return html`
      <div class="config-section authorization">
        <div class="section-header">
          <h2 class="title-large">Authorization strategy</h2>
          <span class="body-medium description">
            It allows you to configure how users access data in your API. After the authorization strategy is set it
            cannot be changed.
          </span>
        </div>
        <div class="row">
          <ui-select
            label="Strategy"
            name="authorizationStrategy"
            .value="${live(authzStrategy)}"
            @change="${this.handleAuthzStrategyChange}"
          >
            <ui-option value="RBAC">
              <div>Role-based authentication</div>
              <div slot="supporting-text">Access is granted based on user roles.</div>
            </ui-option>
            <ui-option value="PBAC" disabled>
              <div>Permission-based authentication</div>
              <div slot="supporting-text">Coming soon</div>
            </ui-option>
          </ui-select>
          ${renderHelp('api', 'authz-strategy-select')}
        </div>
        ${this.renderRoleConfirmation(model)}
      </div>
    `
  }

  protected renderRoleConfirmation(model: ApiModel): TemplateResult | typeof nothing {
    const strategy = model.authorization?.strategy || ''
    if (strategy !== 'RBAC') {
      return nothing
    }
    const prop = this.roleProperty
    if (!prop) {
      return html`<span class="body-small no-property-warning">
        <ui-icon>warning_amber</ui-icon>
        No role property found on the user entity. Please define one on the data domain's User entity to ensure proper
        authorization.
      </span>`
    }
    return html`<span class="body-small property-info">Role property: <strong>${prop.info.getLabel()}</strong></span>`
  }

  protected renderApiAccessConfiguration(): TemplateResult {
    return html`<api-modeling-access-configuration
      .model=${this.model}
      target="api"
      @change=${this.handleAccessConfigChange}
      class="api-access-configuration"
    ></api-modeling-access-configuration>`
  }
}
