import {
  Injectable,
  Injector,
  effect,
  inject,
  signal
} from '@angular/core';
import { PlatformService } from '../platform';
import { type ServerEndpoint, ServerDirectoryFacade } from '../../domains/server-directory';
import {
  type AutoUpdateMode,
  type DesktopUpdateServerContext,
  type DesktopUpdateServerHealthSnapshot,
  type DesktopUpdateServerVersionStatus,
  type DesktopUpdateState,
  type ElectronApi
} from '../platform/electron/electron-api.models';
import { ElectronBridgeService } from '../platform/electron/electron-bridge.service';

interface ServerHealthSnapshot {
  endpointId: string;
  manifestUrl: string | null;
  serverVersion: string | null;
  serverVersionStatus: DesktopUpdateServerVersionStatus;
}

const SERVER_CONTEXT_REFRESH_INTERVAL_MS = 5 * 60_000;

function createInitialState(): DesktopUpdateState {
  return {
    autoUpdateMode: 'auto',
    availableVersions: [],
    configuredManifestUrls: [],
    currentVersion: '0.0.0',
    defaultManifestUrls: [],
    isSupported: false,
    lastCheckedAt: null,
    latestVersion: null,
    manifestUrl: null,
    manifestUrls: [],
    minimumServerVersion: null,
    preferredVersion: null,
    restartRequired: false,
    serverBlocked: false,
    serverBlockMessage: null,
    serverVersion: null,
    serverVersionStatus: 'unknown',
    status: 'idle',
    statusMessage: null,
    targetVersion: null
  };
}

function normalizeOptionalString(value: unknown): string | null {
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
}

function normalizeOptionalHttpUrl(value: unknown): string | null {
  const nextValue = normalizeOptionalString(value);

  if (!nextValue) {
    return null;
  }

  try {
    const parsedUrl = new URL(nextValue);

    return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'
      ? parsedUrl.toString().replace(/\/+$/, '')
      : null;
  } catch {
    return null;
  }
}

function normalizeUrlList(values: readonly unknown[]): string[] {
  const manifestUrls: string[] = [];

  for (const entry of values) {
    const manifestUrl = normalizeOptionalHttpUrl(entry);

    if (!manifestUrl || manifestUrls.includes(manifestUrl)) {
      continue;
    }

    manifestUrls.push(manifestUrl);
  }

  return manifestUrls;
}

@Injectable({ providedIn: 'root' })
export class DesktopAppUpdateService {
  readonly isElectron = inject(PlatformService).isElectron;
  readonly state = signal<DesktopUpdateState>(createInitialState());

  private injector = inject(Injector);
  private servers = inject(ServerDirectoryFacade);
  private electronBridge = inject(ElectronBridgeService);
  private initialized = false;
  private refreshTimerId: number | null = null;
  private removeStateListener: (() => void) | null = null;

  async initialize(): Promise<void> {
    if (!this.isElectron || this.initialized) {
      return;
    }

    this.initialized = true;

    this.setupServerWatcher();
    this.startRefreshTimer();

    const api = this.getElectronApi();

    if (!api) {
      return;
    }

    try {
      const currentState = await api.getAutoUpdateState?.();

      if (currentState) {
        this.state.set(currentState);
      }
    } catch {}

    if (api.onAutoUpdateStateChanged) {
      this.removeStateListener?.();
      this.removeStateListener = api.onAutoUpdateStateChanged((nextState) => {
        this.state.set(nextState);
      });
    }

    await this.refreshServerContext();
  }

  async refreshServerContext(): Promise<void> {
    if (!this.isElectron) {
      return;
    }

    await this.syncServerHealth();
  }

  async checkForUpdates(): Promise<void> {
    const api = this.getElectronApi();

    if (!api?.checkForAppUpdates) {
      return;
    }

    try {
      const nextState = await api.checkForAppUpdates();

      this.state.set(nextState);
    } catch {}
  }

  async saveUpdatePreferences(mode: AutoUpdateMode, preferredVersion: string | null): Promise<void> {
    const api = this.getElectronApi();

    if (!api?.setDesktopSettings) {
      return;
    }

    try {
      await api.setDesktopSettings({
        autoUpdateMode: mode,
        preferredVersion: normalizeOptionalString(preferredVersion)
      });

      if (api.getAutoUpdateState) {
        const nextState = await api.getAutoUpdateState();

        this.state.set(nextState);
      }
    } catch {}
  }

  async saveManifestUrls(manifestUrls: string[]): Promise<void> {
    const api = this.getElectronApi();

    if (!api?.setDesktopSettings) {
      return;
    }

    try {
      await api.setDesktopSettings({
        manifestUrls: normalizeUrlList(manifestUrls)
      });

      if (api.getAutoUpdateState) {
        const nextState = await api.getAutoUpdateState();

        this.state.set(nextState);
      }
    } catch {}
  }

  async restartToApplyUpdate(): Promise<void> {
    const api = this.getElectronApi();

    if (!api?.restartToApplyUpdate) {
      return;
    }

    try {
      await api.restartToApplyUpdate();
    } catch {}
  }

  private setupServerWatcher(): void {
    effect(() => {
      this.servers.servers();

      if (!this.initialized) {
        return;
      }

      void this.syncServerHealth();
    }, { injector: this.injector });
  }

  private startRefreshTimer(): void {
    if (this.refreshTimerId !== null || typeof window === 'undefined') {
      return;
    }

    this.refreshTimerId = window.setInterval(() => {
      void this.refreshServerContext();
    }, SERVER_CONTEXT_REFRESH_INTERVAL_MS);
  }

  private async syncServerHealth(): Promise<void> {
    const api = this.getElectronApi();

    if (!api?.configureAutoUpdateContext) {
      return;
    }

    const endpoints = this.getPrioritizedServers();

    if (endpoints.length === 0) {
      await this.pushContext({
        manifestUrls: [],
        serverVersion: null,
        serverVersionStatus: 'unknown'
      });

      return;
    }

    const healthSnapshots = await Promise.all(
      endpoints.map((endpoint) => this.readServerHealth(endpoint))
    );
    const activeEndpoint = this.servers.activeServer() ?? endpoints[0] ?? null;
    const activeSnapshot = activeEndpoint
      ? healthSnapshots.find((snapshot) => snapshot.endpointId === activeEndpoint.id) ?? null
      : null;

    await this.pushContext({
      manifestUrls: normalizeUrlList(
        healthSnapshots.map((snapshot) => snapshot.manifestUrl)
      ),
      serverVersion: activeSnapshot?.serverVersion ?? null,
      serverVersionStatus: activeSnapshot?.serverVersionStatus ?? 'unknown'
    });
  }

  private getPrioritizedServers(): ServerEndpoint[] {
    const endpoints = [...this.servers.servers()];
    const activeServerId = this.servers.activeServer()?.id ?? null;

    return endpoints.sort((left, right) => {
      if (left.id === activeServerId) {
        return -1;
      }

      if (right.id === activeServerId) {
        return 1;
      }

      return 0;
    });
  }

  private async readServerHealth(endpoint: ServerEndpoint): Promise<ServerHealthSnapshot> {
    const sanitizedServerUrl = endpoint.url.replace(/\/+$/, '');
    const api = this.getElectronApi();

    if (!api?.getAutoUpdateServerHealth) {
      return {
        endpointId: endpoint.id,
        manifestUrl: null,
        serverVersion: null,
        serverVersionStatus: 'unavailable'
      };
    }

    try {
      const payload = await api.getAutoUpdateServerHealth(sanitizedServerUrl);

      return {
        endpointId: endpoint.id,
        ...this.normalizeHealthSnapshot(payload)
      };
    } catch {
      return {
        endpointId: endpoint.id,
        manifestUrl: null,
        serverVersion: null,
        serverVersionStatus: 'unavailable'
      };
    }
  }

  private normalizeHealthSnapshot(
    snapshot: DesktopUpdateServerHealthSnapshot
  ): Omit<ServerHealthSnapshot, 'endpointId'> {
    const serverVersion = normalizeOptionalString(snapshot.serverVersion);

    return {
      manifestUrl: normalizeOptionalHttpUrl(snapshot.manifestUrl),
      serverVersion,
      serverVersionStatus: serverVersion
        ? snapshot.serverVersionStatus
        : snapshot.serverVersionStatus === 'reported'
          ? 'missing'
          : snapshot.serverVersionStatus
    };
  }

  private async pushContext(context: Partial<DesktopUpdateServerContext>): Promise<void> {
    const api = this.getElectronApi();

    if (!api?.configureAutoUpdateContext) {
      return;
    }

    try {
      const nextState = await api.configureAutoUpdateContext(context);

      this.state.set(nextState);
    } catch {}
  }

  private getElectronApi(): ElectronApi | null {
    return this.electronBridge.getApi();
  }
}
