import * as plugins from '../plugins.js';
import * as appstate from '../appstate.js';
import * as interfaces from '../../ts_interfaces/index.js';
import { appRouter } from '../router.js';
import './ob-auth-gate.js';
import {
  DeesElement,
  customElement,
  html,
  state,
  css,
  cssManager,
  type TemplateResult,
} from '@design.estate/dees-element';

interface IUnresolvedView {
  slug?: string;
  name: string;
  iconName?: string;
  element?: Promise<any>;
  subViews?: IUnresolvedView[];
}

interface IResolvedView {
  slug?: string;
  name: string;
  iconName?: string;
  element?: any;
  subViews?: IResolvedView[];
}

@customElement('ob-app-shell')
export class ObAppShell extends DeesElement {
  @state()
  accessor loginState: appstate.ILoginState = { identity: null, phase: 'anonymous', generation: 0 };

  @state()
  accessor uiState: appstate.IUiState = {
    activeView: 'dashboard',
    activeSubview: null,
    autoRefresh: true,
    refreshInterval: 30000,
  };

  @state()
  accessor systemState: appstate.ISystemState = {
    status: null,
    resource: appstate.systemStatePart.getState()!.resource,
  };

  @state()
  accessor globalMessages: plugins.deesCatalog.IGlobalMessage[] = [];

  private viewTabs: IUnresolvedView[] = [
    {
      slug: 'dashboard',
      name: 'Dashboard',
      iconName: 'lucide:layoutDashboard',
      element: (async () => (await import('./ob-view-dashboard.js')).ObViewDashboard)(),
    },
    {
      slug: 'apps',
      name: 'Apps',
      iconName: 'lucide:store',
      subViews: [
        {
          slug: 'app-store',
          name: 'App Store',
          iconName: 'lucide:store',
          element: (async () => (await import('./ob-view-appstore.js')).ObViewAppStore)(),
        },
        {
          slug: 'services',
          name: 'Services',
          iconName: 'lucide:boxes',
          element: (async () => (await import('./ob-view-services.js')).ObViewServices)(),
        },
      ],
    },
    {
      slug: 'network',
      name: 'Network',
      iconName: 'lucide:network',
      subViews: [
        {
          slug: 'proxy',
          name: 'Proxy',
          iconName: 'lucide:route',
          element: (async () => (await import('./ob-view-network.js')).ObViewNetwork)(),
        },
        {
          slug: 'domains',
          name: 'Domains',
          iconName: 'lucide:globe',
          element: (async () => (await import('./ob-view-domains.js')).ObViewDomains)(),
        },
        {
          slug: 'dns-records',
          name: 'DNS Records',
          iconName: 'lucide:listTree',
          element: (async () => (await import('./ob-view-dns-records.js')).ObViewDnsRecords)(),
        },
      ],
    },
    {
      slug: 'registry',
      name: 'Registry',
      iconName: 'lucide:package',
      subViews: [
        {
          slug: 'registries',
          name: 'Registries',
          iconName: 'lucide:package',
          element: (async () => (await import('./ob-view-registries.js')).ObViewRegistries)(),
        },
        {
          slug: 'tokens',
          name: 'Tokens',
          iconName: 'lucide:key',
          element: (async () => (await import('./ob-view-tokens.js')).ObViewTokens)(),
        },
      ],
    },
    {
      slug: 'settings',
      name: 'Settings',
      iconName: 'lucide:settings',
      element: (async () => (await import('./ob-view-settings.js')).ObViewSettings)(),
    },
  ];

  private resolvedViewTabs: IResolvedView[] = [];
  private initializedAppDashes = new WeakSet<HTMLElement>();
  private suppressedUpdateVersion = '';
  private upgradeFlowRunning = false;

  constructor() {
    super();
    document.title = 'Onebox';

    const loginSubscription = appstate.loginStatePart
      .select((stateArg: appstate.ILoginState) => stateArg)
      .subscribe((loginState: appstate.ILoginState) => {
        this.loginState = loginState;
        this.updateGlobalMessages();
        if (appstate.isAuthenticatedSession()) {
          void this.initializeAuthenticatedDashboard(loginState.generation).catch((error) => {
            console.error('Failed to initialize authenticated dashboard:', error);
          });
        }
      });
    this.rxSubscriptions.push(loginSubscription);

    const systemSubscription = appstate.systemStatePart
      .select((stateArg: appstate.ISystemState) => stateArg)
      .subscribe((systemState: appstate.ISystemState) => {
        this.systemState = systemState;
        this.updateGlobalMessages();
      });
    this.rxSubscriptions.push(systemSubscription);

    const uiSubscription = appstate.uiStatePart
      .select((stateArg: appstate.IUiState) => stateArg)
      .subscribe((uiState: appstate.IUiState) => {
        this.uiState = uiState;
        this.syncAppdashView(uiState.activeView, uiState.activeSubview);
      });
    this.rxSubscriptions.push(uiSubscription);
  }

  private async resolveViewTabs(tabs: IUnresolvedView[]): Promise<IResolvedView[]> {
    return Promise.all(
      tabs.map(async (tab) => {
        const resolvedTab: IResolvedView = {
          slug: tab.slug,
          name: tab.name,
          iconName: tab.iconName,
        };
        if (tab.element) {
          resolvedTab.element = await tab.element;
        }
        if (tab.subViews) {
          resolvedTab.subViews = await this.resolveViewTabs(tab.subViews);
        }
        return resolvedTab;
      }),
    );
  }

  private slugFor(view: IResolvedView): string {
    return view.slug ?? view.name.toLowerCase().replace(/\s+/g, '-');
  }

  private findParent(view: IResolvedView): IResolvedView | undefined {
    return this.resolvedViewTabs.find((viewTab) => viewTab.subViews?.includes(view));
  }

  private findViewBySlug(viewSlug: string, subviewSlug: string | null): IResolvedView | undefined {
    const topLevelView = this.resolvedViewTabs.find((view) => this.slugFor(view) === viewSlug);
    if (!topLevelView) return undefined;
    if (subviewSlug && topLevelView.subViews) {
      return topLevelView.subViews.find((subview) => this.slugFor(subview) === subviewSlug) ?? topLevelView;
    }
    return topLevelView;
  }

  private get currentViewTab(): IResolvedView | undefined {
    if (this.resolvedViewTabs.length === 0) return undefined;
    return this.findViewBySlug(this.uiState.activeView, this.uiState.activeSubview) ?? this.resolvedViewTabs[0];
  }

  public static override styles = [
    cssManager.defaultStyles,
    css`
      :host {
        display: block;
        width: 100%;
        height: 100%;
      }
      .maincontainer {
        width: 100%;
        height: 100vh;
      }
      .dashboard {
        width: 100%;
        height: 100%;
      }
      .session-status {
        min-height: 100%;
        display: grid;
        place-items: center;
        color: ${cssManager.bdTheme('#52525b', '#a1a1aa')};
        background: ${cssManager.bdTheme('#fafafa', '#09090b')};
      }
      .session-card {
        padding: 24px;
        border-radius: 12px;
        background: ${cssManager.bdTheme('#ffffff', '#18181b')};
        box-shadow: 0 8px 30px rgb(0 0 0 / 10%);
      }
    `,
  ];

  public override render(): TemplateResult {
    if (this.loginState.phase === 'verifying') {
      return html`
        <div class="maincontainer session-status">
          <div class="session-card">
            ${this.loginState.verificationError
              ? html`
                  <p>${this.loginState.verificationError}</p>
                  <dees-button
                    .text=${'Retry verification'}
                    @click=${() => void appstate.verifyPersistedSession()}
                  ></dees-button>
                `
              : 'Verifying your Onebox session…'}
          </div>
        </div>
      `;
    }
    if (this.loginState.phase === 'anonymous') {
      return html`
        <div class="maincontainer">
          <ob-auth-gate @authenticated=${this.handleAuthenticated}></ob-auth-gate>
        </div>
      `;
    }
    if (this.loginState.phase !== 'authenticated' || !appstate.isAuthenticatedSession()) {
      return html`
        <div class="maincontainer session-status">
          <div class="session-card">Signing out of Onebox…</div>
        </div>
      `;
    }
    return html`
      <div class="maincontainer">
        <div class="dashboard">
          <dees-simple-appdash
            name="Onebox"
            .viewTabs=${this.resolvedViewTabs}
            .selectedView=${this.currentViewTab}
            .globalMessages=${this.globalMessages}
          >
          </dees-simple-appdash>
        </div>
      </div>
    `;
  }

  public override async firstUpdated(): Promise<void> {
    this.resolvedViewTabs = await this.resolveViewTabs(this.viewTabs);
    this.requestUpdate();
    await this.updateComplete;
    await appstate.verifyPersistedSession();
    const loginState = appstate.getRequiredState(appstate.loginStatePart);
    if (appstate.isAuthenticatedSession()) {
      await this.initializeAuthenticatedDashboard(loginState.generation);
    }
  }

  private async initializeAuthenticatedDashboard(generationArg: number): Promise<void> {
    if (!this.isConnected || this.resolvedViewTabs.length === 0) return;
    const beforeRender = appstate.getRequiredState(appstate.loginStatePart);
    if (!appstate.isAuthenticatedSession() || beforeRender.generation !== generationArg) return;
    this.requestUpdate();
    await this.updateComplete;
    const currentSession = appstate.getRequiredState(appstate.loginStatePart);
    if (!appstate.isAuthenticatedSession() || currentSession.generation !== generationArg) return;

    const appDash = this.shadowRoot!.querySelector('dees-simple-appdash') as any;
    if (!appDash || this.initializedAppDashes.has(appDash)) return;
    this.initializedAppDashes.add(appDash);
    appDash.addEventListener('view-select', (eventArg: CustomEvent) => {
      const view = eventArg.detail.view as IResolvedView;
      const parent = this.findParent(view);
      const currentState = appstate.getRequiredState(appstate.uiStatePart);
      if (parent) {
        const parentSlug = this.slugFor(parent);
        const subviewSlug = this.slugFor(view);
        if (currentState.activeView === parentSlug && currentState.activeSubview === subviewSlug) {
          return;
        }
        appRouter.navigateToView(parentSlug, subviewSlug);
      } else {
        const slug = this.slugFor(view);
        if (currentState.activeView === slug && !currentState.activeSubview) return;
        appRouter.navigateToView(slug);
      }
    });
    appDash.addEventListener('logout', () => {
      void appstate.logout();
    });

    const currentUiState = appstate.getRequiredState(appstate.uiStatePart);
    const initialView = this.findViewBySlug(
      currentUiState.activeView,
      currentUiState.activeSubview,
    ) || this.resolvedViewTabs[0];
    await appDash.loadView(initialView);
  }

  private handleAuthenticated = async (
    eventArg: CustomEvent<{ identity: interfaces.data.IIdentity }>,
  ): Promise<void> => {
    await appstate.acceptIdentity(eventArg.detail.identity);
  };

  private updateGlobalMessages(): void {
    const updateStatus = this.systemState.status?.onebox.update;
    if (
      !appstate.isAuthenticatedSession() ||
      !updateStatus?.updateAvailable ||
      !updateStatus.latestVersion ||
      updateStatus.latestVersion === this.suppressedUpdateVersion
    ) {
      this.globalMessages = [];
      return;
    }

    this.globalMessages = [
      {
        id: `onebox-update-${updateStatus.latestVersion}`,
        type: 'info',
        icon: 'lucide:download',
        message: `Onebox ${updateStatus.latestVersion} is available. Current version: ${updateStatus.currentVersion}.`,
        dismissible: false,
        actions: [
          {
            name: 'Update Now',
            iconName: 'lucide:download',
            action: () => this.startOneboxUpgradeFlow(),
          },
          {
            name: 'Release Notes',
            iconName: 'lucide:fileText',
            action: () => this.openUpdateUrl(updateStatus.changelogUrl || updateStatus.releaseUrl),
          },
          {
            name: 'Later',
            iconName: 'lucide:clock',
            action: () => {
              this.suppressedUpdateVersion = updateStatus.latestVersion || '';
              this.updateGlobalMessages();
            },
          },
        ],
      },
    ];
  }

  private async startOneboxUpgradeFlow(): Promise<void> {
    if (this.upgradeFlowRunning) {
      return;
    }

    const updateStatus = this.systemState.status?.onebox.update;
    if (!appstate.isAuthenticatedSession() || !updateStatus?.latestVersion) {
      return;
    }

    this.upgradeFlowRunning = true;
    const updater = await plugins.deesCatalog.DeesUpdater.createAndShow({
      appName: 'Onebox',
      appIcon: 'lucide:box',
      currentVersion: updateStatus.currentVersion,
      updatedVersion: updateStatus.latestVersion,
      updateSource: updateStatus.releaseUrl ? 'Onebox release channel' : '',
      impactItems: [
        'The Onebox service restarts while the new package build is installed.',
        'The browser may briefly lose connection during the restart.',
        'The UI reloads after the new version is verified.',
      ],
      moreInfoUrl: updateStatus.releaseUrl,
      changelogUrl: updateStatus.changelogUrl,
      successAction: 'reload',
      successDelayMs: 30000,
      successActionLabel: 'Reloading Onebox UI',
    });

    try {
      updater.setPhase('checking', {
        percentage: 10,
        indeterminate: true,
        statusText: 'Requesting upgrade...',
        line: 'Requesting Onebox upgrade',
      });

      const response = await appstate.runProtectedRequest(
        'onebox.upgrade.start',
        { targetVersion: updateStatus.latestVersion },
        async (identity) => await new plugins.domtools.plugins.typedrequest.TypedRequest<
          interfaces.requests.IReq_StartOneboxUpgrade
        >('/typedrequest', 'startOneboxUpgrade').fire({ identity }),
        { mode: 'mutation', domain: 'onebox.upgrade' },
      );
      if (!response) return;

      if (!response.upgrade.accepted) {
        updater.markUpdateError(response.upgrade.message);
        return;
      }

      updater.setPhase('downloading', {
        percentage: 35,
        statusText: 'Upgrade accepted...',
        line: response.upgrade.message,
      });
      if (response.upgrade.pid) {
        updater.appendProgressLine(`Upgrade process PID: ${response.upgrade.pid}`);
      }
      if (response.upgrade.logPath) {
        updater.appendProgressLine(`Upgrade log: ${response.upgrade.logPath}`);
      }
      updater.setPhase('installing', {
        percentage: 45,
        indeterminate: true,
        statusText: 'Installer started...',
        line: 'Installer started',
      });

      const verified = await this.waitForOneboxUpgrade(updater, response.upgrade.targetVersion);
      if (verified) {
        await updater.markUpdateReady();
      } else {
        updater.markUpdateWarning(
          `The upgrade command was accepted, but Onebox ${this.normalizeVersion(response.upgrade.targetVersion)} could not be verified before the timeout.`,
          { line: 'Timed out waiting for the version check' },
        );
      }
    } catch (error) {
      updater.markUpdateError(this.getErrorMessage(error));
    } finally {
      this.upgradeFlowRunning = false;
    }
  }

  private async waitForOneboxUpgrade(
    updaterArg: plugins.deesCatalog.DeesUpdater,
    targetVersionArg: string,
  ): Promise<boolean> {
    const normalizedTargetVersion = this.normalizeVersion(targetVersionArg);
    const timeoutAt = Date.now() + 90000;
    let attempt = 0;

    updaterArg.setPhase('restarting', {
      percentage: 55,
      statusText: 'Waiting for Onebox to restart...',
      line: 'Waiting for Onebox to restart with the new version',
    });
    while (Date.now() < timeoutAt) {
      await this.delay(5000);
      attempt++;

      try {
        const response = await appstate.runProtectedRequest(
          'onebox.upgrade.verify',
          { targetVersion: normalizedTargetVersion },
          async (identity) => await new plugins.domtools.plugins.typedrequest.TypedRequest<
            interfaces.requests.IReq_GetSystemStatus
          >('/typedrequest', 'getSystemStatus').fire({ identity }),
          { mode: 'latest' },
        );
        if (!response) return false;
        const onlineVersion = this.normalizeVersion(response.status.onebox.version);
        updaterArg.setPhase('verifyingApp', {
          percentage: Math.min(95, 65 + attempt * 5),
          statusText: `Onebox API answered with ${onlineVersion}.`,
          line: `Onebox API answered with ${onlineVersion}`,
        });

        if (onlineVersion === normalizedTargetVersion) {
          updaterArg.updateProgress({
            percentage: 100,
            indeterminate: false,
            statusText: `Onebox ${normalizedTargetVersion} is online.`,
          });
          return true;
        }
      } catch {
        updaterArg.setPhase('restarting', {
          percentage: Math.min(95, 55 + attempt * 5),
          indeterminate: true,
          statusText: `Waiting for Onebox ${normalizedTargetVersion}...`,
          line: 'Onebox API is restarting...',
        });
      }
    }

    return false;
  }

  private openUpdateUrl(urlArg: string): void {
    window.open(urlArg, '_blank', 'noopener,noreferrer');
  }

  private async delay(millisecondsArg: number): Promise<void> {
    const domtools = await this.domtoolsPromise;
    await domtools.convenience.smartdelay.delayFor(millisecondsArg);
  }

  private getErrorMessage(errorArg: unknown): string {
    return errorArg instanceof Error ? errorArg.message : String(errorArg);
  }

  private normalizeVersion(versionArg: string): string {
    const trimmedVersion = versionArg.trim();
    return trimmedVersion.startsWith('v') ? trimmedVersion : `v${trimmedVersion}`;
  }

  private syncAppdashView(viewName: string, subviewName: string | null): void {
    const appDash = this.shadowRoot?.querySelector('dees-simple-appdash') as any;
    if (!appDash || this.resolvedViewTabs.length === 0) return;

    const targetTab = this.findViewBySlug(viewName, subviewName);
    if (!targetTab || appDash.selectedView === targetTab) return;

    appDash.loadView(targetTab);
  }
}
