@Retry(3)
library news_test;

import 'dart:async';
import 'dart:io';

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

import 'helper.dart';

void main() {
  group('news', () {
    late DockerImage image;
    late HttpServer rssServer;
    setUpAll(() async {
      image = await getDockerImage();
      rssServer = await getRssServer();
    });
    tearDownAll(() async => rssServer.close(force: true));

    late DockerContainer container;
    late TestNextcloudClient client;
    setUp(() async {
      container = await getDockerContainer(image);
      client = await getTestClient(container);
    });
    tearDown(() => container.destroy());

    Future<NewsListFeeds> addWikipediaFeed([final int? folderID]) async => client.news.addFeed(
          url: 'http://host.docker.internal:${rssServer.port}/wikipedia.xml',
          folderId: folderID,
        );

    Future<NewsListFeeds> addNasaFeed() async => client.news.addFeed(
          url: 'http://host.docker.internal:${rssServer.port}/nasa.xml',
        );

    test('Is supported', () async {
      final (supported, _) = await client.news.isSupported();
      expect(supported, isTrue);
    });

    test('Add feed', () async {
      var response = await client.news.listFeeds();
      expect(response.starredCount, 0);
      expect(response.newestItemId, null);
      expect(response.feeds, hasLength(0));

      response = await addWikipediaFeed();
      expect(response.starredCount, null);
      expect(response.newestItemId, isNotNull);
      expect(response.feeds, hasLength(1));
      expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml');

      response = await client.news.listFeeds();
      expect(response.starredCount, 0);
      expect(response.newestItemId, isNotNull);
      expect(response.feeds, hasLength(1));
      expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml');
    });

    test('Rename feed', () async {
      var response = await addWikipediaFeed();
      expect(response.feeds[0].title, 'Wikipedia featured articles feed');

      await client.news.renameFeed(
        feedId: 1,
        feedTitle: 'test1',
      );

      response = await client.news.listFeeds();
      expect(response.feeds[0].title, 'test1');
    });

    test('Move feed to folder', () async {
      await client.news.createFolder(name: 'test1');
      await addWikipediaFeed();
      await client.news.moveFeed(
        feedId: 1,
        folderId: 1,
      );

      final response = await client.news.listFolders();
      expect(response.folders, hasLength(1));
      expect(response.folders[0].id, 1);
      expect(response.folders[0].name, 'test1');
      expect(response.folders[0].opened, true);
      expect(response.folders[0].feeds, hasLength(0));
    });

    test('Mark feed as read', () async {
      final feedsResponse = await addWikipediaFeed();

      var articlesResponse = await client.news.listArticles(type: NewsListType.unread.index);
      expect(articlesResponse.items.length, greaterThan(0));

      await client.news.markFeedAsRead(
        feedId: feedsResponse.feeds[0].id,
        newestItemId: feedsResponse.newestItemId!,
      );

      articlesResponse = await client.news.listArticles(type: NewsListType.unread.index);
      expect(articlesResponse.items, hasLength(0));
    });

    test('List articles', () async {
      var response = await client.news.listArticles();
      expect(response.items, hasLength(0));

      await addWikipediaFeed();

      response = await client.news.listArticles();
      expect(response.items.length, greaterThan(0));
      expect(response.items[0].body, isNotNull);
      expect(response.items[0].feedId, 1);
      expect(response.items[0].unread, true);
      expect(response.items[0].starred, false);
    });

    test('List updated articles', () async {
      // Testing this is not easy, because we can't depend on an external source to update the feed
      // Therefore we just add a second feed and check that the articles returned after a certain modified timestamp
      // are exactly those of the new feed.
      // Now that I think of it, maybe we could host our own feed and update that way, but this works for now.

      await addWikipediaFeed();

      var response = await client.news.listArticles();
      final wikipediaArticles = response.items.length;
      expect(wikipediaArticles, greaterThan(0));

      await addNasaFeed();

      response = await client.news.listArticles();
      final nasaArticles = response.items.length - wikipediaArticles;
      expect(nasaArticles, greaterThan(0));

      response = await client.news.listUpdatedArticles(
        lastModified: response.items[response.items.length - 1 - nasaArticles].lastModified,
      );
      expect(response.items, hasLength(nasaArticles));
    });

    test('Mark article as read', () async {
      await addWikipediaFeed();

      var response = await client.news.listArticles(type: NewsListType.unread.index);
      final unreadArticles = response.items.length;
      expect(unreadArticles, greaterThan(0));

      await client.news.markArticleAsRead(
        itemId: response.items[0].id,
      );
      response = await client.news.listArticles(type: NewsListType.unread.index);
      expect(response.items, hasLength(unreadArticles - 1));
    });

    test('Mark article as unread', () async {
      await addWikipediaFeed();

      var response = await client.news.listArticles(type: NewsListType.unread.index);
      final readArticle = response.items[0];
      await client.news.markArticleAsRead(itemId: readArticle.id);
      response = await client.news.listArticles(type: NewsListType.unread.index);
      final unreadArticles = response.items.length;
      expect(unreadArticles, greaterThan(0));

      await client.news.markArticleAsUnread(itemId: readArticle.id);
      response = await client.news.listArticles(type: NewsListType.unread.index);
      expect(response.items, hasLength(unreadArticles + 1));
    });

    test('Star article', () async {
      await addWikipediaFeed();

      var response = await client.news.listArticles(type: NewsListType.starred.index);
      final starredArticles = response.items.length;
      expect(starredArticles, 0);

      response = await client.news.listArticles();
      await client.news.starArticle(
        itemId: response.items[0].id,
      );
      response = await client.news.listArticles(type: NewsListType.starred.index);
      expect(response.items, hasLength(1));
    });

    test('Unstar article', () async {
      await addWikipediaFeed();

      var response = await client.news.listArticles();
      final item = response.items[0];

      await client.news.starArticle(
        itemId: item.id,
      );
      response = await client.news.listArticles(type: NewsListType.starred.index);
      expect(response.items, hasLength(1));

      await client.news.unstarArticle(
        itemId: item.id,
      );
      response = await client.news.listArticles(type: NewsListType.starred.index);
      expect(response.items, hasLength(0));
    });

    test('Create folder', () async {
      var response = await client.news.listFolders();
      expect(response.folders, hasLength(0));

      response = await client.news.createFolder(name: 'test1');
      expect(response.folders, hasLength(1));
      expect(response.folders[0].id, 1);
      expect(response.folders[0].name, 'test1');
      expect(response.folders[0].opened, true);
      expect(response.folders[0].feeds, hasLength(0));

      response = await client.news.listFolders();
      expect(response.folders, hasLength(1));
      expect(response.folders[0].id, 1);
      expect(response.folders[0].name, 'test1');
      expect(response.folders[0].opened, true);
      expect(response.folders[0].feeds, hasLength(0));
    });

    test('List folders', () async {
      var response = await client.news.listFolders();
      expect(response.folders, hasLength(0));

      await client.news.createFolder(name: 'test1');
      await client.news.createFolder(name: 'test2');

      response = response = await client.news.listFolders();
      expect(response.folders, hasLength(2));
      expect(response.folders[0].id, 1);
      expect(response.folders[0].name, 'test1');
      expect(response.folders[0].opened, true);
      expect(response.folders[0].feeds, hasLength(0));
      expect(response.folders[1].id, 2);
      expect(response.folders[1].name, 'test2');
      expect(response.folders[1].opened, true);
      expect(response.folders[1].feeds, hasLength(0));
    });

    test('Add feed to folder', () async {
      await client.news.createFolder(name: 'test1');
      final response = await addWikipediaFeed(1);
      expect(response.starredCount, null);
      expect(response.newestItemId, isNotNull);
      expect(response.feeds, hasLength(1));
      expect(response.feeds[0].folderId, 1);
      expect(response.feeds[0].url, 'http://host.docker.internal:${rssServer.port}/wikipedia.xml');
    });

    test('Mark folder as read', () async {
      final foldersResponse = await client.news.createFolder(name: 'test1');
      final feedsResponse = await addWikipediaFeed(1);

      var articlesResponse = await client.news.listArticles(type: NewsListType.unread.index);
      expect(articlesResponse.items.length, greaterThan(0));

      await client.news.markFolderAsRead(
        folderId: foldersResponse.folders[0].id,
        newestItemId: feedsResponse.newestItemId!,
      );

      articlesResponse = await client.news.listArticles(type: NewsListType.unread.index);
      expect(articlesResponse.items, hasLength(0));
    });
  });
}

Future<HttpServer> getRssServer() async {
  final wikipediaRss = File('test/files/wikipedia.xml').readAsStringSync();
  final nasaRss = File('test/files/nasa.xml').readAsStringSync();
  while (true) {
    try {
      final port = randomPort();
      final server = await HttpServer.bind(InternetAddress.anyIPv6, port);
      unawaited(
        server.forEach((final request) async {
          switch (request.uri.path) {
            case '/wikipedia.xml':
              request.response.write(wikipediaRss);
            case '/nasa.xml':
              request.response.write(nasaRss);
            default:
              request.response.statusCode = HttpStatus.badRequest;
          }
          await request.response.close();
        }),
      );
      return server;
    } catch (_) {}
  }
}