import 'dart:io';

import 'artifact_staging_service.dart';
import 'jenkins_client.dart';
import 'token_store.dart';

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

enum JenkinsSessionRestoreKind {
  missing,
  restored,
  reauthRequired,
  networkFailure,
  serverFailure,
}

class JenkinsSessionRestoreResult {
  const JenkinsSessionRestoreResult({required this.kind, this.error});

  final JenkinsSessionRestoreKind kind;
  final Object? error;

  bool get isRestored => kind == JenkinsSessionRestoreKind.restored;
}

class JenkinsArtifactSession {
  JenkinsArtifactSession({
    required this.store,
    required this.client,
    required this.stager,
  });

  final TokenStore store;
  final JenkinsClient client;
  final ArtifactStagingService stager;

  JenkinsSessionConfig? _session;

  Future<JenkinsSessionRestoreResult> restoreDetailed() async {
    final session = await store.readSession();
    if (session == null) {
      _session = null;
      return const JenkinsSessionRestoreResult(
        kind: JenkinsSessionRestoreKind.missing,
      );
    }

    try {
      await client.fetchCurrentUser(
        baseUrl: session.baseUrl,
        credentials: session.credentials,
      );
      _session = session;
      return const JenkinsSessionRestoreResult(
        kind: JenkinsSessionRestoreKind.restored,
      );
    } on JenkinsClientException catch (error) {
      if (error.statusCode == 401 || error.statusCode == 403) {
        await store.clearSession();
        _session = null;
        return JenkinsSessionRestoreResult(
          kind: JenkinsSessionRestoreKind.reauthRequired,
          error: error,
        );
      }
      _session = null;
      return JenkinsSessionRestoreResult(
        kind: JenkinsSessionRestoreKind.serverFailure,
        error: error,
      );
    } on IOException catch (error) {
      _session = null;
      return JenkinsSessionRestoreResult(
        kind: JenkinsSessionRestoreKind.networkFailure,
        error: error,
      );
    }
  }

  Future<bool> restore() async => (await restoreDetailed()).isRestored;

  bool get isRestored => _session != null;

  Future<void> clear() async {
    await store.clearSession();
    _session = null;
  }

  Future<List<JenkinsJob>> loadJobs() async {
    final session = _session;
    if (session == null) {
      throw StateError('Jenkins session is not restored');
    }

    return client.fetchJobs(
      baseUrl: session.baseUrl,
      credentials: session.credentials,
    );
  }

  Future<List<JenkinsBuild>> loadBuilds(JenkinsJob job) async {
    final session = _session;
    if (session == null) {
      throw StateError('Jenkins session is not restored');
    }

    return client.fetchRecentBuilds(
      jobUrl: job.url,
      jobName: job.name,
      credentials: session.credentials,
    );
  }

  DownloadTask downloadArtifact(JenkinsBuild build, BuildArtifact artifact) {
    final session = _session;
    if (session == null) {
      throw StateError('Jenkins session is not restored');
    }

    final artifactUrl = _artifactUrl(build: build, artifact: artifact);
    return client.downloadArtifactStream(
      artifactUrl: artifactUrl,
      credentials: session.credentials,
    );
  }

  Future<StagedApk> stageApk({
    required JenkinsBuild build,
    required BuildArtifact artifact,
    required Stream<List<int>> byteStream,
  }) {
    return stager.stageApk(
      build: build,
      artifact: artifact,
      byteStream: byteStream,
    );
  }

  Future<void> cleanup(String path) async => stager.cleanup(path);

  Uri _artifactUrl({
    required JenkinsBuild build,
    required BuildArtifact artifact,
  }) {
    final buildPath = build.url.path;
    final normalizedBuildPath = buildPath.endsWith('/')
        ? buildPath.substring(0, buildPath.length - 1)
        : buildPath;
    final normalizedRelativePath = artifact.relativePath.replaceFirst(
      RegExp(r'^/+'),
      '',
    );

    return build.url.replace(
      path: '$normalizedBuildPath/artifact/$normalizedRelativePath',
    );
  }
}
