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 {
  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, isLoggedIn: false };

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

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

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

  @state()
  accessor loginLoading: boolean = false;

  @state()
  accessor loginError: string = '';

  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 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 (loginState.isLoggedIn) {
          appstate.systemStatePart.dispatchAction(appstate.fetchSystemStatusAction, null);
        }
      });
    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;
      }
    `,
  ];

  public override render(): TemplateResult {
    return html`
      <div class="maincontainer">
        <dees-simple-login name="Onebox">
          <dees-simple-appdash
            name="Onebox"
            .viewTabs=${this.resolvedViewTabs}
            .selectedView=${this.currentViewTab}
            .globalMessages=${this.globalMessages}
          >
          </dees-simple-appdash>
        </dees-simple-login>
      </div>
    `;
  }

  public override async firstUpdated() {
    this.resolvedViewTabs = await this.resolveViewTabs(this.viewTabs);
    this.requestUpdate();
    await this.updateComplete;

    const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
    if (simpleLogin) {
      simpleLogin.addEventListener('login', (e: CustomEvent) => {
        this.login(e.detail.data.username, e.detail.data.password);
      });
    }

    const appDash = this.shadowRoot!.querySelector('dees-simple-appdash') as any;
    if (appDash) {
      appDash.addEventListener('view-select', (e: CustomEvent) => {
        const view = e.detail.view as IResolvedView;
        const parent = this.findParent(view);
        const currentState = appstate.uiStatePart.getState();
        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', async () => {
        await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
      });
    }

    if (appDash && this.resolvedViewTabs.length > 0) {
      const currentUiState = appstate.uiStatePart.getState();
      const initialView =
        this.findViewBySlug(currentUiState.activeView, currentUiState.activeSubview) ||
        this.resolvedViewTabs[0];
      await appDash.loadView(initialView);
    }

    const loginState = appstate.loginStatePart.getState();
    if (loginState.identity?.jwt) {
      if (loginState.identity.expiresAt > Date.now()) {
        this.loginState = loginState;
        if (simpleLogin) {
          await simpleLogin.switchToSlottedContent();
        }
        try {
          const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
            interfaces.requests.IReq_GetSystemStatus
          >('/typedrequest', 'getSystemStatus');
          const response = await typedRequest.fire({ identity: loginState.identity });
          appstate.systemStatePart.setState({ status: response.status });
        } catch (err) {
          console.warn('Stored session invalid, returning to login:', err);
          await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
          if (simpleLogin) {
            window.location.reload();
          }
        }
      } else {
        await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
      }
    }
  }

  private async login(username: string, password: string) {
    const domtools = await this.domtoolsPromise;
    const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
    const form = simpleLogin?.shadowRoot?.querySelector('dees-form') as any;

    if (form) {
      form.setStatus('pending', 'Logging in...');
    }

    const newState = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
      username,
      password,
    });

    if (newState.identity) {
      if (form) {
        form.setStatus('success', 'Logged in!');
      }
      if (simpleLogin) {
        await simpleLogin.switchToSlottedContent();
      }
      await appstate.systemStatePart.dispatchAction(appstate.fetchSystemStatusAction, null);
    } else {
      if (form) {
        form.setStatus('error', 'Login failed!');
        await domtools.convenience.smartdelay.delayFor(2000);
        form.reset();
      }
    }
  }

  private updateGlobalMessages(): void {
    const updateStatus = this.systemState.status?.onebox.update;
    if (
      !this.loginState.isLoggedIn ||
      !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 identity = appstate.loginStatePart.getState().identity;
    const updateStatus = this.systemState.status?.onebox.update;
    if (!identity || !updateStatus?.latestVersion) {
      return;
    }

    this.upgradeFlowRunning = true;
    const updater = await plugins.deesCatalog.DeesUpdater.createAndShow({
      currentVersion: updateStatus.currentVersion,
      updatedVersion: updateStatus.latestVersion,
      moreInfoUrl: updateStatus.releaseUrl,
      changelogUrl: updateStatus.changelogUrl,
      successAction: 'reload',
      successDelayMs: 30000,
      successActionLabel: 'Reloading Onebox UI',
    });

    try {
      updater.updateProgress({
        percentage: 10,
        indeterminate: true,
        statusText: 'Requesting upgrade...',
        terminalLines: ['Requesting Onebox upgrade'],
      });

      const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
        interfaces.requests.IReq_StartOneboxUpgrade
      >('/typedrequest', 'startOneboxUpgrade');
      const response = await typedRequest.fire({ identity });

      if (!response.upgrade.accepted) {
        updater.markUpdateError(response.upgrade.message);
        await this.delay(5000);
        await updater.destroy();
        return;
      }

      updater.appendProgressLine(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.updateProgress({
        percentage: 45,
        indeterminate: true,
        statusText: 'Installer started...',
      });

      await this.waitForOneboxUpgrade(updater, response.upgrade.targetVersion, identity);
      await updater.markUpdateReady();
    } catch (error) {
      updater.markUpdateError(this.getErrorMessage(error));
      await this.delay(5000);
      await updater.destroy();
    } finally {
      this.upgradeFlowRunning = false;
    }
  }

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

    updaterArg.appendProgressLine('Waiting for Onebox to restart with the new version');
    while (Date.now() < timeoutAt) {
      await this.delay(5000);
      attempt++;

      try {
        const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
          interfaces.requests.IReq_GetSystemStatus
        >('/typedrequest', 'getSystemStatus');
        const response = await typedRequest.fire({ identity: identityArg });
        const onlineVersion = this.normalizeVersion(response.status.onebox.version);
        updaterArg.appendProgressLine(`Onebox API answered with ${onlineVersion}`);

        if (onlineVersion === normalizedTargetVersion) {
          updaterArg.updateProgress({
            percentage: 100,
            indeterminate: false,
            statusText: `Onebox ${normalizedTargetVersion} is online.`,
          });
          return;
        }
      } catch {
        updaterArg.appendProgressLine('Onebox API is restarting...');
      }

      updaterArg.updateProgress({
        percentage: Math.min(95, 45 + attempt * 5),
        indeterminate: true,
        statusText: `Waiting for Onebox ${normalizedTargetVersion}...`,
      });
    }

    updaterArg.appendProgressLine('Timed out waiting for the version check; reloading the UI anyway');
  }

  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);
  }
}
