import { inject, Injectable } from '@angular/core';
import { SyncLog } from '../../core/log';
import { SyncProviderManager } from '../../op-log/sync-providers/provider-manager.service';
import { isFileBasedProvider } from '../../op-log/sync/operation-sync.util';
import { FileSyncProvider } from '../../op-log/sync-providers/provider.interface';
import { SyncProviderId } from '../../op-log/sync-providers/provider.const';
import { StateSnapshotService } from '../../op-log/backup/state-snapshot.service';
import { VectorClockService } from '../../op-log/sync/vector-clock.service';
import {
  CLIENT_ID_PROVIDER,
  ClientIdProvider,
} from '../../op-log/util/client-id.provider';
import { FileBasedSyncAdapterService } from '../../op-log/sync-providers/file-based/file-based-sync-adapter.service';
import { CURRENT_SCHEMA_VERSION } from '../../op-log/persistence/schema-migration.service';
import { uuidv7 } from '../../util/uuid-v7';
import { GlobalConfigService } from '../../features/config/global-config.service';
import { clearSessionKeyCache } from '@sp/sync-core';

const LOG_PREFIX = 'FileBasedEncryptionService';

@Injectable({
  providedIn: 'root',
})
export class FileBasedEncryptionService {
  private _providerManager = inject(SyncProviderManager);
  private _stateSnapshotService = inject(StateSnapshotService);
  private _vectorClockService = inject(VectorClockService);
  private _clientIdProvider: ClientIdProvider = inject(CLIENT_ID_PROVIDER);
  private _fileBasedAdapter = inject(FileBasedSyncAdapterService);
  private _globalConfigService = inject(GlobalConfigService);

  async enableEncryption(encryptKey: string): Promise<void> {
    await this._applyEncryption(encryptKey, 'enable');
  }

  async changePassword(newPassword: string): Promise<void> {
    await this._applyEncryption(newPassword, 'change');
  }

  async disableEncryption(): Promise<void> {
    await this._applyEncryption(undefined, 'disable');
  }

  private async _applyEncryption(
    encryptKey: string | undefined,
    action: 'enable' | 'change' | 'disable',
  ): Promise<void> {
    SyncLog.normal(`${LOG_PREFIX}: Starting ${action} for file-based provider...`);

    const isDisable = action === 'disable';

    if (!isDisable && !encryptKey) {
      throw new Error('Encryption password is required');
    }

    const provider = this._providerManager.getActiveProvider();
    if (!provider) {
      throw new Error('No active sync provider. Please enable sync first.');
    }

    if (!isFileBasedProvider(provider)) {
      throw new Error(
        `This operation is only supported for file-based providers (Dropbox, WebDAV, LocalFile). ` +
          `Current provider: ${provider.id}`,
      );
    }

    // After isFileBasedProvider check, we know this is a file-based provider
    const fileProvider = provider as FileSyncProvider<SyncProviderId>;

    if (!(await fileProvider.isReady())) {
      throw new Error('Sync provider is not ready. Please configure sync first.');
    }

    const state = await this._stateSnapshotService.getStateSnapshotAsync();
    const vectorClock = await this._vectorClockService.getCurrentVectorClock();
    const clientId = await this._clientIdProvider.getOrGenerateClientId();

    const existingCfg = await fileProvider.privateCfg.load();

    const baseCfg = this._providerManager.getEncryptAndCompressCfg();
    const adapterCfg = {
      ...baseCfg,
      isEncrypt: !isDisable,
    };

    const adapter = this._fileBasedAdapter.createAdapter(
      fileProvider,
      adapterCfg,
      isDisable ? undefined : encryptKey,
    );

    const result = await adapter.uploadSnapshot(
      state,
      clientId,
      'recovery',
      vectorClock,
      CURRENT_SCHEMA_VERSION,
      !isDisable,
      uuidv7(),
    );

    if (!result.accepted) {
      throw new Error(`Snapshot upload failed: ${result.error}`);
    }

    try {
      if (isDisable) {
        // Use providerManager.setProviderConfig() instead of direct setPrivateCfg()
        // to ensure the currentProviderPrivateCfg$ observable is updated.
        // GHSA-9544: write the intent flag ATOMICALLY with the key removal in the
        // single privateCfg write, so a crash before the global updateSection
        // below cannot leave the durable per-provider state at "encryption on,
        // key gone" — which would then (correctly) block all uploads for a
        // provider the user deliberately switched to plaintext.
        await this._providerManager.setProviderConfig(provider.id, {
          ...existingCfg,
          encryptKey: undefined,
          isEncryptionEnabled: false,
        });
        this._globalConfigService.updateSection('sync', {
          isEncryptionEnabled: false,
          encryptKey: '',
        });
      } else {
        await this._providerManager.setProviderConfig(provider.id, {
          ...existingCfg,
          encryptKey,
          isEncryptionEnabled: true,
        });
        this._globalConfigService.updateSection('sync', {
          isEncryptionEnabled: true,
        });
      }
    } catch (cfgError) {
      SyncLog.err(
        `${LOG_PREFIX}: Failed to update config after successful upload. ` +
          `Server has ${isDisable ? 'unencrypted' : 'encrypted'} data but local config may be stale. ` +
          `Please update encryption settings manually.`,
        cfgError,
      );
      throw cfgError;
    }

    clearSessionKeyCache();

    if (result.serverSeq !== undefined) {
      await adapter.setLastServerSeq(result.serverSeq);
    }

    SyncLog.normal(
      `${LOG_PREFIX}: Encryption ${action} for file-based provider complete.`,
    );
  }
}
