import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:nextcloud/nextcloud.dart';
import 'package:test/test.dart';

import 'helper.dart';

Future main() async {
  await run(await getDockerImage());
}

Future run(final DockerImage image) async {
  group('webdav', () {
    late DockerContainer container;
    late TestNextcloudClient client;
    setUp(() async {
      container = await getDockerContainer(image);
      client = await getTestClient(container);
    });
    tearDown(() => container.destroy());

    test('Fail without username', () async {
      client = await getTestClient(
        container,
        username: null,
      );
      expect(() => client.webdav, throwsException);
    });

    test('Get status', () async {
      final status = await client.webdav.status();
      expect(status.capabilities, containsAll(['1', '3', 'access-control']));
      expect(status.searchCapabilities, hasLength(0));
    });

    test('List directory', () async {
      final files = await client.webdav.ls(
        '/',
        props: {
          WebDavProps.ncHasPreview.name,
          WebDavProps.davContentType.name,
          WebDavProps.davLastModified.name,
          WebDavProps.ocSize.name,
        },
      );
      expect(files, hasLength(8));
      final file = files.singleWhere((final f) => f.name == 'Nextcloud.png');
      expect(file.hasPreview, isTrue);
      expect(file.mimeType, 'image/png');
      expect(file.lastModified!.isBefore(DateTime.now()), isTrue);
      expect(file.size, 50598);
    });

    test('Create directory', () async {
      final response = await client.webdav.mkdir('test');
      expect(response.statusCode, equals(201));
    });

    test('Create directory recursively', () async {
      final response = await client.webdav.mkdirs('test/bla');
      expect(response!.statusCode, equals(201));

      final files = await client.webdav.ls('/test');
      expect(files, hasLength(1));
      expect(files[0].path, '/test/bla/');
    });

    test('Upload files', () async {
      final pngBytes = File('test/files/test.png').readAsBytesSync();
      final txtBytes = File('test/files/test.txt').readAsBytesSync();

      var response = await client.webdav.upload(pngBytes, 'test.png');
      expect(response.statusCode, equals(201));

      response = await client.webdav.upload(txtBytes, 'test.txt');
      expect(response.statusCode, equals(201));

      final files = await client.webdav.ls(
        '/',
        props: {
          WebDavProps.ocSize.name,
        },
      );
      expect(files, hasLength(10));
      final pngFile = files.singleWhere((final f) => f.name == 'test.png');
      final txtFile = files.singleWhere((final f) => f.name == 'test.txt');
      expect(pngFile.size, pngBytes.lengthInBytes);
      expect(txtFile.size, txtBytes.lengthInBytes);
    });

    test('Upload file with modified time', () async {
      final lastModified = DateTime.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch ~/ 1000 * 1000);
      final txtBytes = File('test/files/test.txt').readAsBytesSync();

      final response = await client.webdav.upload(
        txtBytes,
        'test.txt',
        lastModified: lastModified,
      );
      expect(response.statusCode, equals(201));

      final files = await client.webdav.ls(
        '/',
        props: {
          WebDavProps.davLastModified.name,
        },
      );
      final txtFile = files.singleWhere((final f) => f.name == 'test.txt');
      expect(txtFile.lastModified!.millisecondsSinceEpoch, lastModified.millisecondsSinceEpoch);
    });

    test('Copy file', () async {
      final response = await client.webdav.copy(
        'Nextcloud.png',
        'test.png',
      );
      expect(response.statusCode, 201);
      final files = await client.webdav.ls('/');
      expect(files.where((final f) => f.name == 'Nextcloud.png'), hasLength(1));
      expect(files.where((final f) => f.name == 'test.png'), hasLength(1));
    });

    test('Copy file (overwrite fail)', () async {
      await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
      await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');

      expect(
        () => client.webdav.copy('1.txt', '2.txt'),
        throwsA(predicate((final e) => (e! as NextcloudApiException).statusCode == 412)),
      );
    });

    test('Copy file (overwrite success)', () async {
      await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
      await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');

      final response = await client.webdav.copy(
        '1.txt',
        '2.txt',
        overwrite: true,
      );
      expect(response.statusCode, 204);
    });

    test('Move file', () async {
      final response = await client.webdav.move(
        'Nextcloud.png',
        'test.png',
      );
      expect(response.statusCode, 201);
      final files = await client.webdav.ls('/');
      expect(files.where((final f) => f.name == 'Nextcloud.png'), hasLength(0));
      expect(files.where((final f) => f.name == 'test.png'), hasLength(1));
    });

    test('Move file (overwrite fail)', () async {
      await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
      await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');

      expect(
        () => client.webdav.move('1.txt', '2.txt'),
        throwsA(predicate((final e) => (e! as NextcloudApiException).statusCode == 412)),
      );
    });

    test('Move file (overwrite success)', () async {
      await client.webdav.upload(Uint8List.fromList(utf8.encode('1')), '1.txt');
      await client.webdav.upload(Uint8List.fromList(utf8.encode('2')), '2.txt');

      final response = await client.webdav.move(
        '1.txt',
        '2.txt',
        overwrite: true,
      );
      expect(response.statusCode, 204);
    });

    test('Get file props', () async {
      final file = await client.webdav.getProps(
        'Nextcloud.png',
        props: {
          WebDavProps.ncHasPreview.name,
          WebDavProps.davContentType.name,
          WebDavProps.davLastModified.name,
          WebDavProps.ocSize.name,
        },
      );
      expect(file.hasPreview, isTrue);
      expect(file.mimeType, 'image/png');
      expect(file.lastModified!.isBefore(DateTime.now()), isTrue);
      expect(file.size, 50598);
    });

    test('Get directory props', () async {
      final data = Uint8List.fromList(utf8.encode('test'));
      await client.webdav.mkdir('test');
      await client.webdav.upload(data, 'test/test.txt');

      final file = await client.webdav.getProps(
        'test',
        props: {
          WebDavProps.davResourceType.name,
          WebDavProps.davContentType.name,
          WebDavProps.davLastModified.name,
          WebDavProps.ocSize.name,
        },
      );
      expect(file.isDirectory, isTrue);
      expect(file.name, 'test');
      expect(file.mimeType, null);
      expectDateInReasonableTimeRange(file.lastModified!, DateTime.now());
      expect(file.size, data.lengthInBytes);
    });

    test('Filter files', () async {
      final response = await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');
      final id = response.headers['oc-fileid']!.first;
      await client.webdav.updateProps('test.txt', {WebDavProps.ocFavorite.name: '1'});

      final files = await client.webdav.filter(
        '/',
        {
          WebDavProps.ocFavorite.name: '1',
        },
        props: {
          WebDavProps.ocId.name,
          WebDavProps.ocFavorite.name,
        },
      );
      expect(files, hasLength(1));
      final file = files.singleWhere((final e) => e.name == 'test.txt');
      expect(file.id, id);
      expect(file.favorite, isTrue);
    });

    test('Set properties', () async {
      final createdDate = DateTime.utc(1971, 2);
      final createdEpoch = createdDate.millisecondsSinceEpoch / 1000;
      final uploadTime = DateTime.now();

      await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');

      final updated = await client.webdav.updateProps('test.txt', {
        WebDavProps.ocFavorite.name: '1',
        WebDavProps.ncCreationTime.name: '$createdEpoch',
      });
      expect(updated, isTrue);

      final file = await client.webdav.getProps(
        'test.txt',
        props: {
          WebDavProps.ocFavorite.name,
          WebDavProps.ncCreationTime.name,
          WebDavProps.ncUploadTime.name,
        },
      );
      expect(file.favorite, isTrue);
      expect(file.createdDate!.isAtSameMomentAs(createdDate), isTrue);
      expectDateInReasonableTimeRange(file.uploadedDate!, uploadTime);
    });

    test('Set custom properties', () async {
      client.webdav.registerNamespace('http://example.com/ns', 'test');

      await client.webdav.upload(Uint8List.fromList(utf8.encode('test')), 'test.txt');

      final updated = await client.webdav.updateProps('test.txt', {
        'test:custom': 'test-custom-prop-value',
      });
      expect(updated, isTrue);

      final file = await client.webdav.getProps(
        'test.txt',
        props: {
          'test:custom',
        },
      );

      expect(
        file.getProp('test:custom')!.text,
        'test-custom-prop-value',
      );
    });
  });
}