import { consume } from "@lit/context";
import type { HassConfig } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { BINARY_STATE_OFF } from "../../../common/const";
import { relativeTime } from "../../../common/datetime/relative_time";
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import { sanitizeHttpUrl } from "../../../common/url/sanitize-http-url";
import "../../../components/buttons/ha-progress-button";
import "../../../components/ha-alert";
import "../../../components/ha-button";
import "../../../components/ha-faded";
import "../../../components/ha-markdown";
import "../../../components/ha-spinner";
import "../../../components/ha-switch";
import "../../../components/item/ha-row-item";
import "../../../components/progress/ha-progress-bar";
import type { BackupConfig } from "../../../data/backup";
import { fetchBackupConfig } from "../../../data/backup";
import {
  apiContext,
  configContext,
  formattersContext,
  internationalizationContext,
  statesContext,
} from "../../../data/context";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
import type { EntitySources } from "../../../data/entity/entity_sources";
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
import { getSupervisorUpdateConfig } from "../../../data/supervisor/update";
import type { FrontendLocaleData } from "../../../data/translation";
import type { UpdateEntity, UpdateType } from "../../../data/update";
import {
  getUpdateType,
  latestVersionIsSkipped,
  updateButtonIsDisabled,
  UpdateEntityFeature,
  updateIsInstalling,
  updateReleaseNotes,
} from "../../../data/update";
import type {
  HomeAssistant,
  HomeAssistantApi,
  HomeAssistantConfig,
  HomeAssistantFormatters,
  HomeAssistantInternationalization,
} from "../../../types";
import { showAlertDialog } from "../../generic/show-dialog-box";

@customElement("more-info-update")
class MoreInfoUpdate extends LitElement {
  @property({ attribute: false }) public stateObj?: UpdateEntity;

  @state()
  @consumeLocalize()
  private _localize!: LocalizeFunc;

  @state()
  @consume({ context: internationalizationContext, subscribe: true })
  @transform<HomeAssistantInternationalization, FrontendLocaleData>({
    transformer: ({ locale }) => locale,
  })
  private _locale!: FrontendLocaleData;

  @state()
  @consume({ context: formattersContext, subscribe: true })
  private _formatters!: HomeAssistantFormatters;

  @state()
  @consume({ context: apiContext, subscribe: true })
  private _api!: HomeAssistantApi;

  @state()
  @consume({ context: statesContext, subscribe: true })
  private _states!: HomeAssistant["states"];

  @state()
  @consume({ context: configContext, subscribe: true })
  @transform<HomeAssistantConfig, HassConfig>({
    transformer: ({ config }) => config,
  })
  private _config!: HassConfig;

  @state() private _releaseNotes?: string | null;

  @state() private _error?: string;

  @state() private _markdownLoading = true;

  @state() private _backupConfig?: BackupConfig;

  @state() private _createBackup = false;

  @state() private _entitySources?: EntitySources;

  private async _fetchBackupConfig() {
    try {
      const { config } = await fetchBackupConfig(this._api);
      this._backupConfig = config;
    } catch (err) {
      // ignore error, because user will get a manual backup option
      // eslint-disable-next-line no-console
      console.error(err);
    }
  }

  private async _fetchUpdateBackupConfig(type: UpdateType) {
    try {
      const config = await getSupervisorUpdateConfig(this._api);

      // for home assistant and OS updates
      if (this._isHaOrOsUpdate(type)) {
        this._createBackup = config.core_backup_before_update;
        return;
      }

      if (type === "addon") {
        this._createBackup = config.add_on_backup_before_update;
      }
    } catch (err) {
      // ignore error, because user can still set the config
      // eslint-disable-next-line no-console
      console.error(err);
    }
  }

  private async _fetchEntitySources() {
    this._entitySources = await fetchEntitySourcesWithCache({
      callWS: this._api.callWS,
      states: this._states,
    });
  }

  private _isHaOrOsUpdate(type: UpdateType): boolean {
    return ["home_assistant", "home_assistant_os"].includes(type);
  }

  private _computeCreateBackupTexts():
    { title: string; description?: string } | undefined {
    if (
      !this.stateObj ||
      !supportsFeature(this.stateObj, UpdateEntityFeature.BACKUP)
    ) {
      return undefined;
    }

    const updateType = this._entitySources
      ? getUpdateType(this.stateObj, this._entitySources)
      : "generic";

    if (this._isHaOrOsUpdate(updateType)) {
      const isBackupConfigValid =
        !!this._backupConfig &&
        !!this._backupConfig.automatic_backups_configured &&
        !!this._backupConfig.create_backup.password &&
        this._backupConfig.create_backup.agent_ids.length > 0;

      if (!isBackupConfigValid) {
        return {
          title: this._localize(
            "ui.dialogs.more_info_control.update.create_backup.manual"
          ),
          description: this._localize(
            "ui.dialogs.more_info_control.update.create_backup.manual_description"
          ),
        };
      }

      const lastAutomaticBackupDate = this._backupConfig
        ?.last_completed_automatic_backup
        ? new Date(this._backupConfig?.last_completed_automatic_backup)
        : null;
      const now = new Date();

      return {
        title: this._localize(
          "ui.dialogs.more_info_control.update.create_backup.automatic"
        ),
        description: lastAutomaticBackupDate
          ? this._localize(
              "ui.dialogs.more_info_control.update.create_backup.automatic_description_last",
              {
                relative_time: relativeTime(
                  lastAutomaticBackupDate,
                  this._locale,
                  now,
                  true
                ),
              }
            )
          : this._localize(
              "ui.dialogs.more_info_control.update.create_backup.automatic_description_none"
            ),
      };
    }

    // App backup
    if (updateType === "addon") {
      const version = this.stateObj.attributes.installed_version;
      return {
        title: this._localize(
          "ui.dialogs.more_info_control.update.create_backup.app"
        ),
        description: version
          ? this._localize(
              "ui.dialogs.more_info_control.update.create_backup.app_description",
              { version: version }
            )
          : undefined,
      };
    }

    // Fallback to generic UI
    return {
      title: this._localize(
        "ui.dialogs.more_info_control.update.create_backup.generic"
      ),
    };
  }

  protected render() {
    if (
      !this._localize ||
      !this.stateObj ||
      this.stateObj.state === UNAVAILABLE ||
      this.stateObj.state === UNKNOWN
    ) {
      return nothing;
    }

    const createBackupTexts = this._computeCreateBackupTexts();
    const releaseUrl = sanitizeHttpUrl(this.stateObj.attributes.release_url);

    return html`
      <div class="content">
        <div class="summary">
          ${
            this.stateObj.attributes.in_progress
              ? supportsFeature(this.stateObj, UpdateEntityFeature.PROGRESS) &&
                this.stateObj.attributes.update_percentage !== null
                ? html`<ha-progress-bar
                    loading
                    .value=${this.stateObj.attributes.update_percentage}
                  ></ha-progress-bar>`
                : html`<ha-progress-bar indeterminate></ha-progress-bar>`
              : nothing
          }
          <h3>${this.stateObj.attributes.title}</h3>
          ${
            this._error
              ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
              : nothing
          }
          <div class="row">
            <div class="key">
              ${this._formatters.formatEntityAttributeName(
                this.stateObj,
                "installed_version"
              )}
            </div>
            <div class="value">
              ${
                this.stateObj.attributes.installed_version ??
                this._localize("state.default.unavailable")
              }
            </div>
          </div>
          <div class="row">
            <div class="key">
              ${this._formatters.formatEntityAttributeName(
                this.stateObj,
                "latest_version"
              )}
            </div>
            <div class="value">
              ${
                this.stateObj.attributes.latest_version ??
                this._localize("state.default.unavailable")
              }
            </div>
          </div>

          ${
            releaseUrl
              ? html`<div class="row">
                  <div class="key">
                    <a href=${releaseUrl} target="_blank" rel="noreferrer">
                      ${this._localize(
                        "ui.dialogs.more_info_control.update.release_announcement"
                      )}
                    </a>
                  </div>
                </div>`
              : nothing
          }
        </div>
        ${
          supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES) &&
          !this._error
            ? this._releaseNotes === undefined
              ? html`
                  <hr />
                  ${this._markdownLoading ? this._renderLoader() : nothing}
                `
              : this._releaseNotes
                ? html`
                    <hr />
                    <ha-markdown
                      @content-resize=${this._markdownLoaded}
                      .content=${this._releaseNotes}
                      class=${this._markdownLoading ? "hidden" : ""}
                    ></ha-markdown>
                    ${this._markdownLoading ? this._renderLoader() : nothing}
                  `
                : nothing
            : this.stateObj.attributes.release_summary
              ? html`
                  <hr />
                  <ha-markdown
                    @content-resize=${this._markdownLoaded}
                    .content=${this.stateObj.attributes.release_summary}
                    class=${this._markdownLoading ? "hidden" : ""}
                  ></ha-markdown>
                  ${this._markdownLoading ? this._renderLoader() : nothing}
                `
              : nothing
        }
      </div>
      <div class="footer">
        ${
          createBackupTexts
            ? html`
                <ha-row-item>
                  <span slot="headline">${createBackupTexts.title}</span>
                  ${
                    createBackupTexts.description
                      ? html`
                          <span slot="supporting-text">
                            ${createBackupTexts.description}
                          </span>
                        `
                      : nothing
                  }
                  <ha-switch
                    slot="end"
                    .checked=${this._createBackup}
                    @change=${this._createBackupChanged}
                    .disabled=${updateIsInstalling(this.stateObj)}
                  ></ha-switch>
                </ha-row-item>
              `
            : nothing
        }
        <div class="actions">
          ${
            this.stateObj.state === BINARY_STATE_OFF &&
            this.stateObj.attributes.skipped_version
              ? html`
                  <ha-button
                    appearance="plain"
                    @click=${this._handleClearSkipped}
                  >
                    ${this._localize(
                      "ui.dialogs.more_info_control.update.clear_skipped"
                    )}
                  </ha-button>
                `
              : html`
                  <ha-button
                    appearance="plain"
                    @click=${this._handleSkip}
                    .disabled=${
                      latestVersionIsSkipped(this.stateObj) ||
                      this.stateObj.state === BINARY_STATE_OFF ||
                      updateIsInstalling(this.stateObj)
                    }
                  >
                    ${this._localize("ui.dialogs.more_info_control.update.skip")}
                  </ha-button>
                `
          }
          ${
            supportsFeature(this.stateObj, UpdateEntityFeature.INSTALL)
              ? html`
                  <ha-button
                    @click=${this._handleInstall}
                    .loading=${updateIsInstalling(this.stateObj)}
                    .disabled=${updateButtonIsDisabled(this.stateObj)}
                  >
                    ${this._localize(
                      "ui.dialogs.more_info_control.update.update"
                    )}
                  </ha-button>
                `
              : nothing
          }
        </div>
      </div>
    `;
  }

  private _renderLoader() {
    return html`
      <div class="flex center loader">
        <ha-spinner></ha-spinner>
      </div>
    `;
  }

  protected firstUpdated(): void {
    if (supportsFeature(this.stateObj!, UpdateEntityFeature.RELEASE_NOTES)) {
      this._fetchReleaseNotes();
    }
    if (supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
      this._fetchEntitySources().then(() => {
        const type = getUpdateType(this.stateObj!, this._entitySources!);
        if (
          isComponentLoaded(this._config, "hassio") &&
          ["addon", "home_assistant", "home_assistant_os"].includes(type)
        ) {
          this._fetchUpdateBackupConfig(type);
        }

        if (this._isHaOrOsUpdate(type)) {
          this._fetchBackupConfig();
        }
      });
    }
  }

  private async _markdownLoaded() {
    if (this._markdownLoading) {
      this._markdownLoading = false;
    }
  }

  private async _fetchReleaseNotes() {
    try {
      this._releaseNotes = await updateReleaseNotes(
        this._api,
        this.stateObj!.entity_id
      );
    } catch (err: any) {
      this._error = err.message;
    }
  }

  get _shouldCreateBackup(): boolean {
    if (!supportsFeature(this.stateObj!, UpdateEntityFeature.BACKUP)) {
      return false;
    }
    return this._createBackup;
  }

  private _handleInstall(): void {
    const installData: Record<string, any> = {
      entity_id: this.stateObj!.entity_id,
    };

    if (this._shouldCreateBackup) {
      installData.backup = true;
    }

    if (
      supportsFeature(this.stateObj!, UpdateEntityFeature.SPECIFIC_VERSION) &&
      this.stateObj!.attributes.latest_version
    ) {
      installData.version = this.stateObj!.attributes.latest_version;
    }

    this._api.callService("update", "install", installData);
  }

  private _createBackupChanged(ev) {
    this._createBackup = ev.target.checked;
  }

  private _handleSkip(): void {
    if (this.stateObj!.attributes.auto_update) {
      showAlertDialog(this, {
        title: this._localize(
          "ui.dialogs.more_info_control.update.auto_update_enabled_title"
        ),
        text: this._localize(
          "ui.dialogs.more_info_control.update.auto_update_enabled_text"
        ),
      });
      return;
    }
    this._api.callService("update", "skip", {
      entity_id: this.stateObj!.entity_id,
    });
  }

  private _handleClearSkipped(): void {
    this._api.callService("update", "clear_skipped", {
      entity_id: this.stateObj!.entity_id,
    });
  }

  static styles = css`
    :host {
      display: flex;
      flex-direction: column;
      flex: 1;
      justify-content: space-between;
    }
    hr {
      border-color: var(--divider-color);
      border-bottom: none;
      margin: var(--ha-space-4) 0;
    }
    ha-expansion-panel {
      margin: var(--ha-space-4) 0;
    }

    .summary {
      margin-bottom: var(--ha-space-4);
    }

    .row {
      margin: 0;
      display: flex;
      flex-direction: row;
      justify-content: space-between;
    }

    .footer {
      border-top: 1px solid var(--divider-color);
      background: var(
        --ha-dialog-surface-background,
        var(--mdc-theme-surface, #fff)
      );
      position: sticky;
      bottom: 0;
      margin: 0 calc(var(--ha-space-6) * -1) 0 calc(var(--ha-space-6) * -1);
      margin-bottom: calc(
        -1 * max(var(--safe-area-inset-bottom), var(--ha-space-6))
      );
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      align-items: center;
      overflow: hidden;
      z-index: 10;
    }

    ha-row-item {
      width: 100%;
      --ha-row-item-padding-inline: var(--ha-space-6);
    }

    .actions {
      width: 100%;
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      justify-content: flex-end;
      box-sizing: border-box;
      padding: var(--ha-space-4);
      z-index: 1;
      gap: var(--ha-space-2);
    }

    a {
      color: var(--primary-color);
    }
    .flex.center {
      display: flex;
      justify-content: center;
      align-items: center;
    }
    ha-markdown {
      direction: ltr;
      padding-bottom: var(--ha-space-4);
      box-sizing: border-box;
    }
    ha-markdown.hidden {
      display: none;
    }
    .loader {
      height: 80px;
      box-sizing: border-box;
      padding-bottom: var(--ha-space-4);
    }
  `;
}

declare global {
  interface HTMLElementTagNameMap {
    "more-info-update": MoreInfoUpdate;
  }
}
