import '../infrastructure/local/local.dart';
import '../infrastructure/remote/remote.dart';
import '../infrastructure/security/security.dart';
import 'application.dart';

abstract interface class AppDependencies {
  AuthUseCases get authUseCases;
  ExerciseUseCases get exerciseUseCases;
  MediaUseCases get mediaUseCases;
  ProgramUseCases get programUseCases;
  WorkoutTemplateUseCases get workoutTemplateUseCases;
  ActiveWorkoutSessionUseCases get activeWorkoutSessionUseCases;
  ActiveExerciseStepUseCases get activeExerciseStepUseCases;
  CloseWorkoutSessionUseCase get closeWorkoutSessionUseCase;
  WorkoutHistoryUseCases get workoutHistoryUseCases;
  ProgressionStatsUseCase get progressionStatsUseCase;
  ExercisePerformanceReferenceUseCase get exercisePerformanceReferenceUseCase;
  DataExportUseCase get dataExportUseCase;
  DataImportUseCase get dataImportUseCase;
  SyncUseCases get syncUseCases;
  ShareUseCases get shareUseCases;
}

final class AppBootstrap implements AppDependencies {
  AppBootstrap._({
    required this.database,
    required this.authUseCases,
    required this.exerciseUseCases,
    required this.mediaUseCases,
    required this.programUseCases,
    required this.workoutTemplateUseCases,
    required this.activeWorkoutSessionUseCases,
    required this.activeExerciseStepUseCases,
    required this.closeWorkoutSessionUseCase,
    required this.workoutHistoryUseCases,
    required this.progressionStatsUseCase,
    required this.exercisePerformanceReferenceUseCase,
    required this.dataExportUseCase,
    required this.dataImportUseCase,
    required this.syncUseCases,
    required this.shareUseCases,
    required this.syncGateway,
  });

  final AppDatabase database;
  @override
  final AuthUseCases authUseCases;
  @override
  final ExerciseUseCases exerciseUseCases;
  @override
  final MediaUseCases mediaUseCases;
  @override
  final ProgramUseCases programUseCases;
  @override
  final WorkoutTemplateUseCases workoutTemplateUseCases;
  @override
  final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases;
  @override
  final ActiveExerciseStepUseCases activeExerciseStepUseCases;
  @override
  final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
  @override
  final WorkoutHistoryUseCases workoutHistoryUseCases;
  @override
  final ProgressionStatsUseCase progressionStatsUseCase;
  @override
  final ExercisePerformanceReferenceUseCase exercisePerformanceReferenceUseCase;
  @override
  final DataExportUseCase dataExportUseCase;
  @override
  final DataImportUseCase dataImportUseCase;
  @override
  final SyncUseCases syncUseCases;
  @override
  final ShareUseCases shareUseCases;
  final SyncGateway syncGateway;

  static Future<AppBootstrap> create() async {
    final database = AppDatabase.open();
    await database.customSelect('SELECT 1').get();
    final exerciseRepository = DriftExerciseRepository(database);
    final mediaRepository = DriftMediaAssetRepository(database);
    final onlineAccountRepository = DriftOnlineAccountRepository(database);
    final syncMetadataRepository = DriftSyncMetadataRepository(database);
    final mappingRepository = DriftRemoteResourceMappingRepository(database);
    final localSyncChangeRepository = DriftLocalSyncChangeRepository(database);
    final starterSeedRepository = DriftStarterSeedRepository(database);
    final shareInboxRepository = DriftShareInboxRepository(database);
    final pendingShareActionRepository = DriftPendingShareActionRepository(
      database,
    );
    final programRepository = DriftProgramRepository(database);
    final templateRepository = DriftWorkoutTemplateRepository(database);
    final activeSessionRepository = DriftActiveSessionRepository(database);
    final historyRepository = DriftWorkoutHistoryRepository(database);
    final progressionStatsRepository = DriftProgressionStatsRepository(
      database,
    );
    final performanceReferenceRepository =
        DriftExercisePerformanceReferenceRepository(database);
    final localDataBackupRepository = DriftLocalDataBackupRepository(database);
    const localBackupMediaStore = PathProviderLocalMediaStorage();
    final ids = LocalIdGenerator();
    const clock = SystemClock();
    const originDeviceId = 'local-device';
    await SeedStarterContentUseCase(
      seedStateRepository: starterSeedRepository,
      contentRepository: starterSeedRepository,
      clock: clock,
      originDeviceId: originDeviceId,
    ).run();
    final remoteAuthApi = HttpRemoteAuthApi(
      HttpApiClient(baseUrl: Uri.parse(HttpApiClient.defaultBaseUrl)),
    );
    final remoteSyncApi = HttpRemoteSyncApi(
      HttpApiClient(baseUrl: Uri.parse(HttpApiClient.defaultBaseUrl)),
    );
    final remoteShareApi = HttpRemoteShareApi(
      HttpApiClient(baseUrl: Uri.parse(HttpApiClient.defaultBaseUrl)),
    );

    return AppBootstrap._(
      database: database,
      authUseCases: AuthUseCases(
        tokenStore: const SecureStorageAuthTokenStore(),
        accountRepository: onlineAccountRepository,
        remoteAuthApi: remoteAuthApi,
        clock: clock,
        ids: ids,
      ),
      exerciseUseCases: ExerciseUseCases(
        repository: exerciseRepository,
        programRepository: programRepository,
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      mediaUseCases: MediaUseCases(
        mediaRepository: mediaRepository,
        exerciseRepository: exerciseRepository,
        storage: const PathProviderLocalMediaStorage(),
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      programUseCases: ProgramUseCases(
        programRepository: programRepository,
        exerciseRepository: exerciseRepository,
        templateRepository: templateRepository,
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      workoutTemplateUseCases: WorkoutTemplateUseCases(
        templateRepository: templateRepository,
        programRepository: programRepository,
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      activeWorkoutSessionUseCases: ActiveWorkoutSessionUseCases(
        sessionRepository: activeSessionRepository,
        templateRepository: templateRepository,
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      activeExerciseStepUseCases: ActiveExerciseStepUseCases(
        sessionRepository: activeSessionRepository,
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      closeWorkoutSessionUseCase: CloseWorkoutSessionUseCase(
        sessionRepository: activeSessionRepository,
        historyRepository: historyRepository,
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      workoutHistoryUseCases: WorkoutHistoryUseCases(
        repository: historyRepository,
        clock: clock,
      ),
      progressionStatsUseCase: ProgressionStatsUseCase(
        repository: progressionStatsRepository,
        clock: clock,
      ),
      exercisePerformanceReferenceUseCase: ExercisePerformanceReferenceUseCase(
        repository: performanceReferenceRepository,
      ),
      dataExportUseCase: DataExportUseCase(
        repository: localDataBackupRepository,
        mediaStore: localBackupMediaStore,
        clock: clock,
      ),
      dataImportUseCase: DataImportUseCase(
        repository: localDataBackupRepository,
        mediaStore: localBackupMediaStore,
        clock: clock,
      ),
      syncUseCases: SyncUseCases(
        tokenStore: const SecureStorageAuthTokenStore(),
        remoteSyncApi: remoteSyncApi,
        metadataRepository: syncMetadataRepository,
        mappingRepository: mappingRepository,
        localChanges: localSyncChangeRepository,
        clock: clock,
        deviceId: originDeviceId,
      ),
      shareUseCases: ShareUseCases(
        tokenStore: const SecureStorageAuthTokenStore(),
        remoteShareApi: remoteShareApi,
        inboxRepository: shareInboxRepository,
        pendingActionRepository: pendingShareActionRepository,
        localChanges: localSyncChangeRepository,
        programRepository: programRepository,
        templateRepository: templateRepository,
        clock: clock,
        ids: ids,
        originDeviceId: originDeviceId,
      ),
      syncGateway: const NoOpSyncGateway(),
    );
  }

  Future<void> dispose() => database.close();
}
