import { version } from '../../../../package.json';
import {
  HTTP_INTERCEPTORS,
  provideHttpClient,
  withInterceptorsFromDi,
} from '@angular/common/http';
import {
  DEFAULT_CURRENCY_CODE,
  ErrorHandler,
  Injector,
  LOCALE_ID,
  NgModule,
  inject,
  provideAppInitializer,
  signal,
} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PlatformModule } from '@angular/cdk/platform';

import { Config } from '@core/config';
import { AuthModule, AuthService, LoginStrategy } from '@core/auth';
import { CoreCommandModule } from '@core/command';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {
  ApplicationService,
  ApplicationServiceAdapter,
  CoreApplicationModule,
} from '@core/application';
import { AppStoreModule } from './app-store.module';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { AppSwaggerModule } from './app-swagger.module';
import { AppDomainModule } from './app-domain.module';
import { UiModalModule } from '@ui/modal';
import {
  NotificationsHubModule,
  NOTIFICATIONS_HUB_OPTIONS,
} from '@hub/notifications';
import { SignalRHubOptions } from '@core/signalr';
import { CoreBreadcrumbModule } from '@core/breadcrumb';
import { UiCommonModule } from '@ui/common';
import { registerLocaleData } from '@angular/common';

import localeDe from '@angular/common/locales/de';
import localeDeExtra from '@angular/common/locales/extra/de';
import { HttpErrorInterceptor } from './interceptors';
import { CoreLoggerModule, LOG_PROVIDER } from '@core/logger';
import { IsaLogProvider } from './providers';
import { IsaErrorHandler } from './providers/isa.error-handler';
import {
  ScanAdapterModule,
  ScanAdapterService,
  ScanditScanAdapterModule,
} from '@adapter/scan';
import * as Commands from './commands';
import { PreviewComponent } from './preview';
import { NativeContainerService } from '@external/native-container';
import { ShellModule } from '@shared/shell';
import { MainComponent } from './main.component';
import { IconModule } from '@shared/components/icon';
import { NgIconsModule } from '@ng-icons/core';
import {
  matClose,
  matWifi,
  matWifiOff,
} from '@ng-icons/material-icons/baseline';
import { NetworkStatusService } from './services/network-status.service';
import { debounceTime, filter, firstValueFrom, switchMap } from 'rxjs';
import { provideMatomo } from 'ngx-matomo-client';
import { withRouter, withRouteData } from 'ngx-matomo-client';
import {
  provideLogging,
  withLogLevel,
  LogLevel,
  withSink,
  ConsoleLogSink,
  logger as loggerFactory,
} from '@isa/core/logging';
import {
  IDBStorageProvider,
  provideUserSubFactory,
  UserStorageProvider,
} from '@isa/core/storage';
import { Store } from '@ngrx/store';
import { OAuthService } from 'angular-oauth2-oidc';
import z from 'zod';
import { TabNavigationService } from '@isa/core/tabs';

registerLocaleData(localeDe, localeDeExtra);
registerLocaleData(localeDe, 'de', localeDeExtra);

export function _appInitializerFactory(config: Config, injector: Injector) {
  return async () => {
    // Get logging service for initialization logging
    const logger = loggerFactory(() => ({ service: 'AppInitializer' }));
    const statusElement = document.querySelector('#init-status');
    const laoderElement = document.querySelector('#init-loader');

    try {
      logger.info('Starting application initialization');

      let online = false;
      const networkStatus = injector.get(NetworkStatusService);
      while (!online) {
        online = await firstValueFrom(networkStatus.online$);

        if (!online) {
          logger.warn('Waiting for network connection');
          statusElement.innerHTML =
            '<b>Warte auf Netzwerkverbindung (WLAN)</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br>Sobald eine Netzwerkverbindung besteht, wird die App automatisch neu geladen.';
          await new Promise((resolve) => setTimeout(resolve, 250));
        }
      }

      logger.info('Network connection established');

      statusElement.innerHTML = 'Konfigurationen werden geladen...';
      logger.info('Loading configurations');

      statusElement.innerHTML = 'Scanner wird initialisiert...';
      logger.info('Initializing scanner');
      const scanAdapter = injector.get(ScanAdapterService);
      await scanAdapter.init();
      logger.info('Scanner initialized');

      statusElement.innerHTML = 'Authentifizierung wird geprüft...';
      logger.info('Initializing authentication');

      const auth = injector.get(AuthService);
      try {
        const authenticated = await auth.init();
        if (!authenticated) {
          throw new Error('User is not authenticated');
        }
      } catch {
        statusElement.innerHTML = 'Authentifizierung wird durchgeführt...';
        logger.info('Performing login');
        const strategy = injector.get(LoginStrategy);
        await strategy.login();
        return;
      }

      statusElement.innerHTML = 'Native Container wird initialisiert...';
      logger.info('Initializing native container');
      const nativeContainer = injector.get(NativeContainerService);
      await nativeContainer.init();
      logger.info('Native container initialized');

      statusElement.innerHTML = 'Datenbank wird initialisiert...';
      logger.info('Initializing database');
      await injector.get(IDBStorageProvider).init();
      logger.info('Database initialized');

      statusElement.innerHTML = 'Benutzerzustand wird geladen...';
      logger.info('Loading user storage');
      const userStorage = injector.get(UserStorageProvider);
      await userStorage.init();

      const store = injector.get(Store);
      // Hydrate Ngrx Store
      const state = userStorage.get('store');
      if (state && state['version'] === version) {
        store.dispatch({ type: 'HYDRATE', payload: userStorage.get('store') });
        logger.info('Store hydrated from user storage');
      } else {
        logger.debug('Store hydration skipped', () => ({
          reason: state ? 'version mismatch' : 'no stored state',
        }));
      }
      // Subscribe on Store changes and save to user storage
      auth.initialized$
        .pipe(
          filter((initialized) => initialized),
          switchMap(() => store.pipe(debounceTime(1000))),
        )
        .subscribe((state) => {
          userStorage.set('store', state);
        });

      logger.info('Application initialization completed');
      // Inject tab navigation service to initialize it
      injector.get(TabNavigationService).init();
    } catch (error) {
      logger.error('Application initialization failed', error as Error, () => ({
        message: (error as Error).message,
      }));
      laoderElement.remove();
      statusElement.classList.add('text-xl');
      statusElement.innerHTML +=
        '⚡<br><br><b>Fehler bei der Initialisierung</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br><br>';

      const reload = document.createElement('button');
      reload.classList.add(
        'bg-brand',
        'text-white',
        'p-2',
        'rounded',
        'cursor-pointer',
      );
      reload.innerHTML = 'App neu laden';
      reload.onclick = () => window.location.reload();
      statusElement.appendChild(reload);

      const preLabel = document.createElement('div');
      preLabel.classList.add('mt-12');
      preLabel.innerHTML = 'Fehlermeldung:';

      statusElement.appendChild(preLabel);

      const pre = document.createElement('pre');
      pre.classList.add('mt-4', 'text-wrap');
      pre.innerHTML = error.message;

      statusElement.appendChild(pre);

      console.error('Error during app initialization', error);
      throw error;
    }
  };
}

export function _notificationsHubOptionsFactory(
  config: Config,
  auth: AuthService,
): SignalRHubOptions {
  const options = { ...config.get('hubs').notifications };
  options.httpOptions.accessTokenFactory = () => auth.getToken();
  return options;
}

const USER_SUB_FACTORY = () => {
  const _logger = loggerFactory(() => ({
    context: 'USER_SUB',
  }));
  const auth = inject(OAuthService);

  const claims = auth.getIdentityClaims();

  if (!claims || typeof claims !== 'object' || !('sub' in claims)) {
    const err = new Error('No valid identity claims found. User is anonymous.');
    _logger.error(err.message);
    throw err;
  }

  const validation = z.string().safeParse(claims['sub']);

  if (!validation.success) {
    const err = new Error('Invalid "sub" claim in identity claims.');
    _logger.error(err.message, { claims });
    throw err;
  }

  return signal(validation.data);
};

@NgModule({
  declarations: [AppComponent, MainComponent],
  bootstrap: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ShellModule.forRoot(),
    AppRoutingModule,
    AppSwaggerModule,
    AppDomainModule,
    CoreBreadcrumbModule.forRoot(),
    CoreCommandModule.forRoot(Object.values(Commands)),
    CoreLoggerModule.forRoot(),
    AppStoreModule,
    PreviewComponent,
    AuthModule.forRoot(),
    CoreApplicationModule.forRoot(),
    UiModalModule.forRoot(),
    UiCommonModule.forRoot(),
    NotificationsHubModule.forRoot(),
    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled: environment.production,
      registrationStrategy: 'registerWhenStable:30000',
    }),
    ScanAdapterModule.forRoot(),
    ScanditScanAdapterModule.forRoot(),
    PlatformModule,
    IconModule.forRoot(),
    NgIconsModule.withIcons({ matWifiOff, matClose, matWifi }),
  ],
  providers: [
    provideAppInitializer(() => {
      const initializerFn = _appInitializerFactory(
        inject(Config),
        inject(Injector),
      );
      return initializerFn();
    }),
    {
      provide: NOTIFICATIONS_HUB_OPTIONS,
      useFactory: _notificationsHubOptionsFactory,
      deps: [Config, AuthService],
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpErrorInterceptor,
      multi: true,
    },
    {
      provide: LOG_PROVIDER,
      useClass: IsaLogProvider,
      multi: true,
    },
    {
      provide: ErrorHandler,
      useClass: IsaErrorHandler,
    },
    {
      provide: ApplicationService,
      useClass: ApplicationServiceAdapter,
    },
    { provide: LOCALE_ID, useValue: 'de-DE' },
    provideHttpClient(withInterceptorsFromDi()),
    provideMatomo(
      { trackerUrl: 'https://matomo.paragon-data.net', siteId: '1' },
      withRouter(),
      withRouteData(),
    ),
    provideLogging(withLogLevel(LogLevel.Debug), withSink(ConsoleLogSink)),
    {
      provide: DEFAULT_CURRENCY_CODE,
      useValue: 'EUR',
    },
    provideUserSubFactory(USER_SUB_FACTORY),
  ],
})
export class AppModule {}
