import 'dart:io';

import 'package:path_provider/path_provider.dart';

import '../models/jenkins_build.dart';

class StagedApk {
  const StagedApk({required this.path, required this.sizeBytes});

  final String path;
  final int sizeBytes;
}

class ArtifactVerifyException implements Exception {
  const ArtifactVerifyException(this.message);

  final String message;

  @override
  String toString() => 'ArtifactVerifyException: $message';
}

class ArtifactStagingService {
  ArtifactStagingService({Directory? stagingDir}) : _stagingDir = stagingDir;

  final Directory? _stagingDir;

  Future<Directory> _resolveDir() async {
    if (_stagingDir != null) return _stagingDir;
    return getTemporaryDirectory();
  }

  static String _sanitize(String value) =>
      value.replaceAll(RegExp(r'[/\\]'), '_');

  String _fileName(JenkinsBuild build, BuildArtifact artifact) {
    final safeJob = _sanitize(build.jobName);
    final safeFile = _sanitize(artifact.fileName);
    return '${safeJob}_${build.number}_$safeFile';
  }

  /// Downloads bytes from [byteStream] into a temp file and returns a [StagedApk].
  /// On cancel or error the partial file is deleted and the exception is rethrown.
  Future<StagedApk> stageApk({
    required JenkinsBuild build,
    required BuildArtifact artifact,
    required Stream<List<int>> byteStream,
  }) async {
    final dir = await _resolveDir();
    final file = File('${dir.path}/${_fileName(build, artifact)}');

    if (await file.exists()) {
      await file.delete();
    }

    final sink = file.openWrite();
    try {
      await for (final chunk in byteStream) {
        sink.add(chunk);
      }
    } catch (e) {
      await sink.close();
      if (await file.exists()) {
        await file.delete();
      }
      rethrow;
    }

    await sink.close();

    try {
      return await verify(file.path);
    } on ArtifactVerifyException {
      if (await file.exists()) {
        await file.delete();
      }
      rethrow;
    }
  }

  /// Verifies an already-staged file: must exist, have .apk extension, and be non-empty.
  Future<StagedApk> verify(String path) async {
    final file = File(path);

    if (!await file.exists()) {
      throw ArtifactVerifyException('File does not exist: $path');
    }

    if (!path.toLowerCase().endsWith('.apk')) {
      throw ArtifactVerifyException('Not an APK file: $path');
    }

    final size = await file.length();
    if (size == 0) {
      throw ArtifactVerifyException('File is empty: $path');
    }

    return StagedApk(path: path, sizeBytes: size);
  }

  /// Deletes the staged file. Call after install completes or is cancelled.
  Future<void> cleanup(String path) async {
    final file = File(path);
    if (await file.exists()) {
      await file.delete();
    }
  }
}
