import { html, TemplateResult, CSSResult, nothing } from 'lit';
import { Events as CoreEvents } from '@api-client/core/build/browser.js';
import { reactive } from '../../lib/decorators.js';
import { navigate } from '../../lib/route.js';
import { IConfigEnvironment, IEnvConfig, IConfigInit, DataSourceType, ConfigInitReason, ConfigBroadcast } from '../../lib/config/Config.js';
import globalStyles from '../../styles/global-styles.js';
import styles from './StoreConfig.styles.js';
import typography from '../../styles/m3/typography.module.js';
import colorStyles from '../../styles/m3/colors.module.js';
import dialogStyles from '../../styles/m3/dialog.module.js';
import surfaceStyles from '../../styles/m3/surface.module.js';
import { IRoute, IRouteResult } from '../../mixins/RouteMixin.js';
import type { AddConfigEnvironmentInit } from '../../bindings/base/ConfigurationBindings.js';
import { UiDialogClosingReason } from '../../ui/dialog/UiDialog.js';
import { ApplicationInit, ApplicationScreen } from '../ApplicationScreen.js';
import RadioElement from '../../ui/input/RadioElement.js';
import Input from '../../ui/input/Input.js';
import '@github/relative-time-element';
import '../../define/dialog/rename-dialog.js';
import '../../define/dialog/confirm-delete-dialog.js';
import '../../define/ui/ui-icon.js';
import '../../define/ui/ui-button.js';
import '../../define/ui/ui-dropdown-list.js';
import '../../define/ui/ui-list.js';
import '../../define/ui/ui-list-item.js';
import '../../define/ui/ui-progress.js';
import '../../define/ui/ui-dialog.js';
import '../../define/ui/ui-radio.js';
import '../../define/ui/ui-text-field.js';
import '../../define/app-navigation-item.js';

export default class StoreConfigScreen extends ApplicationScreen {
  override async storeChanged(): Promise<void> {
    // ...
  }

  static get styles(): CSSResult[] {
    return [globalStyles, typography, colorStyles, dialogStyles, styles, surfaceStyles];
  }

  static get routes(): IRoute[] {
    return [
      { pattern: '/start', method: 'startRoute', fallback: true, name: 'Store welcome', title: 'Store Configuration Welcome' },
      { pattern: '/store/(?<key>.*)', method: 'storeRoute', name: 'Store configuration', title: 'Store configuration' },
      { pattern: '/new', method: 'newRoute', name: 'New store configuration', title: 'New store configuration' },
      { pattern: '*', method: 'telemetryRoute' },
    ];
  }

  /**
   * The selected store configuration
   */
  @reactive() protected selected?: string;

  /**
   * Whether the current environment is a default environment
   */
  @reactive() protected isDefault = false;

  /**
   * Flag set when the screen is requesting the list of environments.
   */
  @reactive() protected loadingEnvironments = false;

  /**
   * The list of application environments.
   */
  @reactive() protected config?: IEnvConfig;

  /**
   * The selected store type.
   */
  @reactive() storeType?: DataSourceType;

  /**
   * Whether the dialog with the help message is opened.
   */
  @reactive() infoDialogOpened = false;

  /**
   * A network store validation error, if any.
   */
  @reactive() validationError?: string;

  /**
   * Whether the application is performing the store validation.
   */
  @reactive() validating = false;

  /**
   * Init reason to pass with the create event.
   */
  initReason: ConfigInitReason = 'add';

  /**
   * The list of environments to render.
   */
  get environments(): IConfigEnvironment[] {
    const { config } = this;
    const envs = config?.environments;
    if (!envs) {
      return [];
    }
    return envs;
  }

  private firstRunInternal = false;

  get firstRun(): boolean {
    return this.firstRunInternal;
  }

  set firstRun(value: boolean) {
    if (this.firstRunInternal === value) {
      return;
    }
    this.firstRunInternal = value;
    if (value) {
      document.body.classList.add('first-run');
    } else {
      document.body.classList.remove('first-run');
    }
    this.render();
  }

  constructor(init: ApplicationInit) {
    super(init);
  }

  override async initialize(): Promise<void> {
    if (!await this.isPlatformSupported()) {
      return;
    }

    await this.bindings.store.initialize();
    await this.bindings.store.initializeStore();
    await this.bindings.config.initialize();

    await this.initializeConfig();
    
    try {
      await this.initializeStore(false);
    } catch (e) {
      // ...
    }
    await this.listEnvironments();
    this.setupFirstRun();
    
    const url = new URL(window.location.href);
    if (url.searchParams.has('init-reason')) {
      this.initReason = url.searchParams.get('init-reason') as ConfigInitReason;
    } else {
      this.initReason = 'add';
    }

    this.initializeRouting();
    this.initialized = true;
  }

  setupFirstRun(): void {
    const { environments } = this;
    this.firstRun = environments.length === 0;
  }

  async listEnvironments(): Promise<void> {
    this.loadingEnvironments = true;
    this.config = undefined;
    try {
      this.config = await this.bindings.config.environment.list();
    } catch (e) {
      const err = e as Error;
      CoreEvents.Telemetry.exception(err.message, true);
      this.reportCriticalError('Unable to load the list of environments.');
    } finally {
      this.loadingEnvironments = false;
    }
  }

  protected resetRoute(): void {
    this.page = undefined;
    this.selected = undefined;
    this.env = undefined;
    this.isDefault = false;
  }

  protected startRoute(): void {
    this.resetRoute();
    this.page = 'start';
  }

  protected storeRoute(info: IRouteResult): void {
    if (!info.params || !info.params.key) {
      throw new Error(`Invalid route configuration. Missing parameters.`);
    }
    this.resetRoute();
    const key = info.params.key as string;
    this.page = 'store';
    this.selected = key;
    const { config } = this;
    if (config && config.environments) {
      const env = config.environments.find(i => i.key === key);
      this.env = env;
      this.isDefault = !!env && env.key === config.current;
    }
  }

  protected newRoute(): void {
    this.resetRoute();
    this.page = 'new';
  }

  protected telemetryRoute(info: IRouteResult): void {
    CoreEvents.Telemetry.view(info.route.name || info.route.pattern || '/');
  }

  protected handleSourceSelected(e: Event): void {
    const radio = e.target as RadioElement;
    this.storeType = radio.value as DataSourceType;
  }

  protected handleOpenHelp(e: Event): void {
    e.preventDefault();
    this.infoDialogOpened = true;
  }

  protected handleDialogClose(): void {
    this.infoDialogOpened = false;
  }

  protected handleContinue(): void {
    const info = this.createInit();
    if (!info) {
      return;
    }
    this.validateInput(info);
  }

  protected handleCancel(): void {
    window.history.back();
  }

  protected createInit(): IConfigInit | undefined {
    if (!this.storeType) {
      return undefined;
    }
    const isNetwork = this.storeType === 'network-store';
    let location: string | undefined;
    if (isNetwork) {
      const input = document.querySelector('.store-url') as Input;
      if (!input.checkValidity()) {
        return undefined;
      }
      location = input.value.trim();
      if (!location) {
        return undefined;
      }
      if (location.endsWith('/')) {
        location = location.substring(0, location.length - 1);
      }
    }
    const info: IConfigInit = {
      source: this.storeType,
      location,
      reason: this.initReason as ConfigInitReason,
    };
    return info;
  }

  protected async validateInput(init: IConfigInit): Promise<void> {
    if (init.source === 'network-store') {
      this.validating = true;
      const error = await this.validateNetworkStore(init.location as string);
      this.validating = false;
      if (error) {
        this.validationError = error;
        return;
      }
    }
    await this.storeEnvironment(init);
  }

  protected async storeEnvironment(init: IConfigInit): Promise<void> {
    const { config } = this;
    const envs = config?.environments;
    const hasEnvironments = !!envs && !!envs.length;
    if (!hasEnvironments) {
      // eslint-disable-next-line no-param-reassign
      init.asDefault = true;
    }
    await this.bindings.store.environment.init(init);
  }

  protected async validateNetworkStore(baseUri: string): Promise<string | undefined> {
    try {
      const info = await this.bindings.store.environment.readStoreInfo(baseUri);
      if (!info) {
        throw new Error(`Unable to connect.`);
      }
    } catch (e) {
      return (e as Error).message;
    }
    return undefined;
  }

  protected override handleConfigBroadcast(e: MessageEvent): void {
    const event = e.data as ConfigBroadcast;
    const { path } = event;
    if (path === 'environment.setDefault') {
      this.handleDefaultEnvironmentSet(event.env as IConfigEnvironment);
    } else if (path === 'environment.add') {
      this.handleEnvironmentAdd(event.env as IConfigEnvironment, event.init as AddConfigEnvironmentInit);
    } else if (path === 'environment.remove') {
      this.handleEnvironmentRemoved(event.id as string);
    } else if (path === 'environment.update') {
      this.handleEnvironmentUpdated(event.env as IConfigEnvironment);
    }
  }

  protected handleEnvironmentUpdated(env: IConfigEnvironment): void {
    const { config } = this;
    if (!env || !config) {
      return;
    }
    if (!config.environments) {
      config.environments = [];
    }
    const index = config.environments.findIndex(i => i.key === env.key);
    if (index >= 0) {
      config.environments[index] = env;
    } else {
      config.environments.push(env);
    }
    if (this.env && this.env.key === env.key) {
      this.env = env;
    }
    this.render();
  }

  protected handleEnvironmentRemoved(id: string): void {
    const { config } = this;
    if (!id || !config) {
      return;
    }
    if (!config.environments) {
      return;
    }
    const index = config.environments.findIndex(i => i.key === id);
    if (index >= 0) {
      config.environments.splice(index, 1);
      this.render();
    }
    if (this.selected === id) {
      navigate('start');
    }
    this.setupFirstRun();
  }

  protected override handleDefaultEnvironmentSet(env: IConfigEnvironment): void {
    const { config } = this;
    if (!env || !config) {
      return;
    }
    this.revalidateCurrent(env.key);
  }

  protected override handleEnvironmentAdd(env: IConfigEnvironment, init: AddConfigEnvironmentInit = {}): void {
    const { config } = this;
    if (!config) {
      return;
    }
    if (!config.environments) {
      config.environments = [];
    }
    config.environments.push(env);
    if (init.asDefault) {
      this.revalidateCurrent(env.key);
    }
    if (['new', 'start'].includes(this.page as string)) {
      navigate('store', env.key);
    } else {
      this.render();
    }
    this.setupFirstRun();
  }

  protected revalidateCurrent(id: string): void {
    const { config } = this;
    if (!config) {
      throw new Error(`Config is not set.`);
    }
    config.current = id;
    if (this.env) {
      this.isDefault = this.env.key === id;
    }
  }

  protected handleOption(e: CustomEvent): void {
    const element = e.detail.item as HTMLElement;
    const { option } = element.dataset;
    if (!option) {
      return;
    }
    switch (option) {
      case 'rename': this.renameEnvironment(); break;
      case 'make-default': this.makeEnvironmentDefault(); break;
      case 'delete': this.deleteEnvironment(); break;
      case 'authenticate': this.authenticateEnvironment(); break;
      default:
    }
  }

  protected renameEnvironment(): void {
    const { env } = this;
    if (!env) {
      throw new Error(`Environment not set`);
    }
    const dialog = document.createElement('rename-dialog');
    dialog.name = env.name;
    dialog.modal = true;
    dialog.open = true;
    document.body.appendChild(dialog);
    dialog.addEventListener('close', (ev: Event) => {
      document.body.removeChild(dialog);
      const event = ev as CustomEvent<UiDialogClosingReason>;
      const { cancelled, value } = event.detail;
      if (!cancelled && value) {
        this.bindings.config.environment.update({ ...env, name: value as string });
      }
    });
  }

  protected deleteEnvironment(): void {
    const { env } = this;
    if (!env) {
      throw new Error(`Environment not set`);
    }
    const dialog = document.createElement('confirm-delete-dialog');
    dialog.open = true;
    dialog.modal = true;
    const message = document.createElement('span');
    message.textContent = 'Are you sure you want to delete this store configuration?';
    dialog.appendChild(message);
    document.body.appendChild(dialog);
    dialog.addEventListener('close', (ev: Event) => {
      document.body.removeChild(dialog);
      const event = ev as CustomEvent<UiDialogClosingReason>;
      const { cancelled } = event.detail;
      if (!cancelled) {
        this.bindings.config.environment.remove(env.key);
      }
    });
  }

  protected async makeEnvironmentDefault(): Promise<void> {
    const { selected } = this;
    if (!selected) {
      return;
    }
    await this.bindings.config.environment.setDefault(selected);
  }

  protected async authenticateEnvironment(): Promise<void> {
    const { env } = this;
    if (!env) {
      return;
    }
    await this.bindings.store.auth.authenticate({
      force: true, 
      updateEnvironment: true,
      env, 
    });
  }

  protected handleAddNewEnvironment(): void {
    navigate('new');
  }

  protected storeNavHandler(e: Event): void {
    const target = e.target as HTMLElement;
    const { key } = target.dataset;
    if (!key) {
      return;
    }
    navigate('store', key)
  }

  override pageTemplate(): TemplateResult {
    const { initialized } = this;
    if (!initialized) {
      return super.pageTemplate();
    }
    if (this.firstRun) {
      return this.renderFirstRun();
    }
    return html`
      ${this.headerTemplate()}
      ${this.navigationTemplate()}
      ${this.mainTemplate()}
      ${this.renderHelpDialog()}
    `;
  }

  protected renderFirstRun(): TemplateResult {
    return html`
    ${this.renderFirstRunHeader()}
    <main class="surface1">
      ${this.addNewTemplate()}
    </main>
    ${this.renderHelpDialog()}
    `;
  }

  protected renderFirstRunHeader(): TemplateResult {
    return html`
    <header class="start-page-header">
      <h1 class="headline-medium first-run">Welcome to API Client</h1>
    </header>
    `;
  }

  protected override headerTemplate(): TemplateResult {
    return html`
    <header class="start-page-header">
      <h1 class="title-large">Store configuration</h1>
    </header>
    `;
  }

  protected override navigationTemplate(): TemplateResult {
    const { config } = this;
    const envs = config?.environments;
    const hasEnvironments = !!envs && !!envs.length;
    return html`
    <nav aria-label="existing stores">
      <ui-button title="Opens a form to create a new environment" @click="${this.handleAddNewEnvironment}">
        <ui-icon icon="add" slot="icon"></ui-icon>
        Add new
      </ui-button>
      ${hasEnvironments ? this.navigationConfigTemplate(envs) : nothing}
    </nav>
    `;
  }

  protected navigationConfigTemplate(envs: IConfigEnvironment[]): TemplateResult {
    return html`
    <ol class="nav-list">
      ${envs.map(i => this.navigationItemTemplate(i))}
    </ol>
    `;
  }

  protected navigationItemTemplate(item: IConfigEnvironment): TemplateResult {
    const { selected, config } = this;
    const isDefault = !!config && item.key === config.current;
    const name = item.name || 'Unnamed configuration';
    // const needsAuth = item.source === 'network-store' && !item.authenticated;
    return html`
      <li>
        <app-navigation-item data-key="${item.key}" .selected=${selected === item.key} @click="${this.storeNavHandler}">
          ${isDefault ? html`<ui-icon icon="taskAlt" slot="icon" title="Default configuration">` : ''}
          ${name}
        </app-navigation-item>
      </li>
    `;
  }

  protected renderAddButton(): TemplateResult {
    return html`<ui-button 
      type="tonal"
      class="add-button" 
      title="Opens a form to create a new environment"
      @click="${this.handleAddNewEnvironment}"
    >Add new</ui-button>`;
  }

  protected override mainTemplate(): TemplateResult {
    let template: TemplateResult;
    switch (this.page) {
      case 'store': template = this.storeTemplate(); break;
      case 'new': template = this.addNewTemplate(); break;
      default:
        template = this.startTemplate(); break;
    }
    return html`
    <main class="surface1">
      ${this.renderProgress()}
      ${template}
    </main>
    `;
  }

  protected startTemplate(): TemplateResult {
    const { config } = this;
    if (!config || !config.environments || !config.environments.length) {
      return html`
      <p class="body-large">It looks like you have no stores configured.</p>
      `;
    }
    return html`
    <p class="body-large">Store configuration allows you to manage connections to different API stores.</p>
    `;
  }

  protected storeTemplate(): TemplateResult {
    const { config, env } = this;
    if (!config || !env) {
      return html`Invalid state, config not ready.`;
    }
    const isDefault = env.key === config.current;
    const isLocal = env.source === 'local-store';
    return html`
    <section>
      <div class="title-area">
        <h2 class="title-medium text-selectable">${env.name || 'Unnamed configuration'}</h2>
        ${this.environmentOptionsTemplate()}
      </div>
      <dl class="body-medium">
        <dt>Key:</dt>
        <dd class="text-selectable">${env.key}</dd>

        <dt>URI</dt>
        <dd class="text-selectable">${env.location}</dd>

        <dt>Source</dt>
        <dd class="text-selectable">${isLocal ? 'Local store' : 'Network store'}</dd>

        <dt>Default environment</dt>
        <dd>${isDefault ? 'Yes' : 'No'}</dd>

        ${isLocal ? '' : this.storeSessionInfo(env)}
    </dl>
    </section>
    `;
  }

  protected storeSessionInfo(env: IConfigEnvironment): TemplateResult {
    const { tokenExpires } = env;
    const time = tokenExpires ? new Date(tokenExpires) : undefined;
    return html`
    <dt>Authenticated</dt>
    <dd>${env.authenticated ? 'Yes' : 'No'}</dd>

    <dt>Token expires</dt>
    <dd>${time ? html`<relative-time datetime="${time.toISOString()}"></relative-time>` : 'Unknown'}</dd>
    `;
  }

  protected environmentOptionsTemplate(): TemplateResult {
    return html`
    <ui-dropdown-list
      verticalAlign="top"
      @select="${this.handleOption}"
      class="environment-dropdown"
      closeOnOutsideClick
    >
      <ui-icon-button aria-label="Press to select available 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="rename" image="icon" title="Renames this environment">
          <ui-icon role="presentation" slot="start" icon="rename"></ui-icon>
          Rename
        </ui-list-item>
        <ui-list-item role="menuitem" data-option="make-default" image="icon" title="Marks this environment as default" ?disabled="${this.isDefault}">
          <ui-icon role="presentation" slot="start" icon="checkBox"></ui-icon>
          Make default
        </ui-list-item>
        <ui-list-item role="menuitem" data-option="authenticate" image="icon" title="Authenticate the environment">
          <ui-icon role="presentation" slot="start" icon="key"></ui-icon>
          Authenticate
        </ui-list-item>
        <ui-list-item role="menuitem" data-option="delete" image="icon" title="Deletes the environment">
          <ui-icon role="presentation" slot="start" icon="deleteOutline"></ui-icon>
          Delete
        </ui-list-item>
      </ui-list>
    </ui-dropdown-list>
    `;
  }

  protected addNewTemplate(): TemplateResult {
    return html`
    <h2 class="title-medium">Configure data store</h2>
    <form class="form">
      ${this.renderIntro()}
      ${this.renderStoreType()}
      ${this.renderStoreUrlInput()}
      ${this.renderProgress()}
      ${this.renderValidationError()}
    </form>
    ${this.renderActions()}
    `;
  }

  renderIntro(): TemplateResult {
    return html`
    <div class="body-medium">
      <p>
        Select the type of the store the application will use. For a single user we recommend the <i>Local store</i> option.
      </p>
      <a href="#" @click="${this.handleOpenHelp}">Help me decide.</a>
    </div>
    `;
  }

  renderStoreType(): TemplateResult {
    return html`
    <div class="data-source">
      <div>
        <label class="label-medium">
          <ui-radio name="source" value="local-store" .checked="${this.storeType === 'local-store'}" @change="${this.handleSourceSelected}"></ui-radio>
          Local store
        </label>
      </div>
      <div>
        <label class="label-medium">
          <ui-radio name="source" value="network-store" .checked="${this.storeType === 'network-store'}" @change="${this.handleSourceSelected}"></ui-radio>
          Network store
        </label>
      </div>
    </div>
    `;
  }

  renderStoreUrlInput(): TemplateResult | string {
    if (this.storeType !== 'network-store') {
      return '';
    }
    return html`
    <div class="store-url-input">
      <ui-text-field
        class="store-url"
        type="url"
        name="location"
        required
        invalidText="The entered URL is invalid. Please, enter the address of the data store."
        supportingText="Enter the address to the store"
        label="Store address"
      ></ui-text-field>
    </div>
    `;
  }

  renderActions(): TemplateResult {
    const canCancel = this.initReason !== 'first-run';
    return html`
    <div class="actions">
      ${canCancel ? html`<ui-button
        type="text"
        @click="${this.handleCancel}"
      >Cancel</ui-button>` : nothing}
      <ui-button 
        type="filled"
        ?disabled="${!this.storeType}" 
        @click="${this.handleContinue}"
      >Continue</ui-button>
    </div>
    `;
  }

  renderHelpDialog(): TemplateResult {
    return html`
    <ui-dialog 
      ?open="${this.infoDialogOpened}" 
      @close="${this.handleDialogClose}" 
      class="help-dialog"
      modal
    >
      <ui-icon slot="icon" icon="help"></ui-icon>
      
      <span slot="title">Selecting Data Source</span>
      
      <section>
        <div class="label-large">Local Store</div>
        <p class="body-large">
          This will install a service on your machine that serves data to the API Client suite. 
          You can access your data only on this device.
        </p>
        <div class="label-large">Network Store</div>
        <p class="body-large">
          Allows you to connect to a store that is in your local network or over internet. 
          You will need the store address to continue.
        </p>
      </section>

      <ui-button slot="button" value="confirm" type="text">close</ui-button>
    </ui-dialog>
    `;
  }

  renderValidationError(): TemplateResult | string {
    const { validationError } = this;
    if (!validationError) {
      return '';
    }
    return html`
    <p class="error on-error-text validation-error">
      ${this.validationError}<br/>
      Please, verify the store URI and try again.
    </p>
    `;
  }

  renderProgress(): TemplateResult | string {
    const { validating } = this;
    if (!validating) {
      return '';
    }
    return html`
    <ui-progress indeterminate></ui-progress>
    `;
  }
}
