import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:neon/blocs.dart';
import 'package:neon/models.dart';
import 'package:neon/theme.dart';
import 'package:neon/utils.dart';
import 'package:neon/widgets.dart';
import 'package:neon_dashboard/l10n/localizations.dart';
import 'package:neon_dashboard/src/widgets/widget.dart';
import 'package:neon_dashboard/src/widgets/widget_button.dart';
import 'package:neon_dashboard/src/widgets/widget_item.dart';
import 'package:nextcloud/dashboard.dart' as dashboard;
import 'package:rxdart/rxdart.dart';

class MockAccountsBloc extends Mock implements AccountsBloc {}

class MockCacheManager extends Mock implements DefaultCacheManager {}

Widget wrapWidget(final AccountsBloc accountsBloc, final Widget child) => MaterialApp(
      localizationsDelegates: DashboardLocalizations.localizationsDelegates,
      supportedLocales: DashboardLocalizations.supportedLocales,
      home: Scaffold(
        backgroundColor: Colors.transparent,
        body: NeonProvider<AccountsBloc>.value(
          value: accountsBloc,
          child: child,
        ),
      ),
    );

void main() {
  NeonCachedImage.cacheManager = MockCacheManager();

  final accountsBloc = MockAccountsBloc();
  when(() => accountsBloc.activeAccount).thenAnswer(
    (final invocation) => BehaviorSubject.seeded(
      Account(
        serverURL: Uri(),
        username: 'example',
      ),
    ),
  );

  group('Widget item', () {
    final item = dashboard.WidgetItem(
      (final b) => b
        ..title = 'Widget item title'
        ..subtitle = 'Widget item subtitle'
        ..link = 'https://example.com/link'
        ..iconUrl = 'https://example.com/iconUrl'
        ..overlayIconUrl = 'https://example.com/overlayIconUrl'
        ..sinceId = '',
    );

    testWidgets('Everything filled', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetItem(
            item: item,
            roundIcon: true,
          ),
        ),
      );

      expect(find.text('Widget item title'), findsOneWidget);
      expect(find.text('Widget item subtitle'), findsOneWidget);
      expect(find.byType(InkWell), findsOneWidget);
      expect(
        tester.widget(find.byType(InkWell)),
        isA<InkWell>().having(
          (final a) => a.onTap,
          'onTap is not null',
          isNotNull,
        ),
      );
      expect(find.byType(NeonImageWrapper), findsOneWidget);
      expect(
        tester.widget(find.byType(NeonImageWrapper)),
        isA<NeonImageWrapper>().having(
          (final a) => a.borderRadius,
          'borderRadius is correct',
          BorderRadius.circular(largeIconSize),
        ),
      );
      expect(find.byType(NeonCachedImage), findsNWidgets(2));

      await expectLater(find.byType(DashboardWidgetItem), matchesGoldenFile('goldens/widget_item.png'));
    });

    testWidgets('Not round', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetItem(
            item: item,
            roundIcon: false,
          ),
        ),
      );

      expect(
        tester.widget(find.byType(NeonImageWrapper)),
        isA<NeonImageWrapper>().having(
          (final a) => a.borderRadius,
          'borderRadius is null',
          null,
        ),
      );

      await expectLater(find.byType(DashboardWidgetItem), matchesGoldenFile('goldens/widget_item_not_round.png'));
    });

    testWidgets('Without link', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetItem(
            item: item.rebuild((final b) => b..link = ''),
            roundIcon: true,
          ),
        ),
      );

      expect(
        tester.widget(find.byType(InkWell)),
        isA<InkWell>().having(
          (final a) => a.onTap,
          'onTap is null',
          isNull,
        ),
      );
    });

    testWidgets('Without overlayIconUrl', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetItem(
            item: item.rebuild((final b) => b..overlayIconUrl = ''),
            roundIcon: true,
          ),
        ),
      );

      expect(find.byType(NeonCachedImage), findsOneWidget);
    });
  });

  group('Widget button', () {
    final button = dashboard.Widget_Buttons(
      (final b) => b
        ..type = 'new'
        ..text = 'Button'
        ..link = 'https://example.com/link',
    );

    testWidgets('New', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetButton(
            button: button,
          ),
        ),
      );

      expect(find.byIcon(Icons.add), findsOneWidget);
      expect(find.text('Button'), findsOneWidget);

      await expectLater(find.byType(DashboardWidgetButton), matchesGoldenFile('goldens/widget_button_new.png'));
    });

    testWidgets('More', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetButton(
            button: button.rebuild((final b) => b.type = 'more'),
          ),
        ),
      );

      expect(find.byIcon(Icons.more_outlined), findsOneWidget);
      expect(find.text('Button'), findsOneWidget);

      await expectLater(find.byType(DashboardWidgetButton), matchesGoldenFile('goldens/widget_button_more.png'));
    });

    testWidgets('Setup', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetButton(
            button: button.rebuild((final b) => b.type = 'setup'),
          ),
        ),
      );

      expect(find.byIcon(Icons.launch), findsOneWidget);
      expect(find.text('Button'), findsOneWidget);

      await expectLater(find.byType(DashboardWidgetButton), matchesGoldenFile('goldens/widget_button_setup.png'));
    });

    testWidgets('Invalid', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidgetButton(
            button: button.rebuild((final b) => b.type = 'test'),
          ),
        ),
      );

      expect(find.byType(Icon), findsNothing);
      expect(find.text('Button'), findsOneWidget);

      await expectLater(find.byType(DashboardWidgetButton), matchesGoldenFile('goldens/widget_button_invalid.png'));
    });
  });

  group('Widget', () {
    final item = dashboard.WidgetItem(
      (final b) => b
        ..title = 'Widget item title'
        ..subtitle = 'Widget item subtitle'
        ..link = 'https://example.com/link'
        ..iconUrl = 'https://example.com/iconUrl'
        ..overlayIconUrl = 'https://example.com/overlayIconUrl'
        ..sinceId = '',
    );
    final items = dashboard.WidgetItems(
      (final b) => b
        ..items = BuiltList<dashboard.WidgetItem>.from([item]).toBuilder()
        ..emptyContentMessage = ''
        ..halfEmptyContentMessage = '',
    );
    final button = dashboard.Widget_Buttons(
      (final b) => b
        ..type = 'new'
        ..text = 'Button'
        ..link = 'https://example.com/link',
    );
    final widget = dashboard.Widget(
      (final b) => b
        ..id = 'id'
        ..title = 'Widget title'
        ..order = 0
        ..iconClass = ''
        ..iconUrl = 'https://example.com/iconUrl'
        ..widgetUrl = 'https://example.com/widgetUrl'
        ..itemIconsRound = true
        ..itemApiVersions = BuiltList<int>.from([1, 2]).toBuilder()
        ..reloadInterval = 0
        ..buttons = BuiltList<dashboard.Widget_Buttons>.from([button]).toBuilder(),
    );

    testWidgets('Everything filled', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidget(
            widget: widget,
            items: items,
          ),
        ),
      );

      expect(find.text('Widget title'), findsOneWidget);
      expect(find.byType(InkWell), findsNWidgets(4));
      expect(
        tester.widget(find.byType(InkWell).first),
        isA<InkWell>().having(
          (final a) => a.onTap,
          'onTap is not null',
          isNotNull,
        ),
      );
      expect(find.byType(NeonImageWrapper), findsOneWidget);
      expect(
        tester.widget(find.byType(NeonImageWrapper)),
        isA<NeonImageWrapper>().having(
          (final a) => a.borderRadius,
          'borderRadius is correct',
          BorderRadius.circular(largeIconSize),
        ),
      );
      expect(find.byType(NeonCachedImage), findsNWidgets(3));
      expect(find.byType(DashboardWidgetItem), findsOneWidget);
      expect(find.bySubtype<FilledButton>(), findsOneWidget);
      expect(find.byIcon(Icons.add), findsOneWidget);
      expect(find.text('Button'), findsOneWidget);

      await expectLater(find.byType(DashboardWidget), matchesGoldenFile('goldens/widget.png'));
    });

    testWidgets('Without widgetUrl', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidget(
            widget: widget.rebuild((final b) => b.widgetUrl = ''),
            items: items,
          ),
        ),
      );

      expect(
        tester.widget(find.byType(InkWell).first),
        isA<InkWell>().having(
          (final a) => a.onTap,
          'onTap is null',
          isNull,
        ),
      );
    });

    testWidgets('Not round', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidget(
            widget: widget.rebuild((final b) => b.itemIconsRound = false),
            items: items,
          ),
        ),
      );

      expect(
        tester.widget(find.byType(NeonImageWrapper)),
        isA<NeonImageWrapper>().having(
          (final a) => a.borderRadius,
          'borderRadius is null',
          null,
        ),
      );

      await expectLater(find.byType(DashboardWidget), matchesGoldenFile('goldens/widget_not_round.png'));
    });

    testWidgets('With halfEmptyContentMessage', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidget(
            widget: widget,
            items: items.rebuild((final b) => b.halfEmptyContentMessage = 'Half empty'),
          ),
        ),
      );

      expect(find.text('Half empty'), findsOneWidget);
      expect(find.byIcon(Icons.check), findsOneWidget);

      await expectLater(find.byType(DashboardWidget), matchesGoldenFile('goldens/widget_with_half_empty.png'));
    });

    testWidgets('With emptyContentMessage', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidget(
            widget: widget,
            items: items.rebuild((final b) => b.emptyContentMessage = 'Empty'),
          ),
        ),
      );

      expect(find.text('Empty'), findsOneWidget);
      expect(find.byIcon(Icons.check), findsOneWidget);

      await expectLater(find.byType(DashboardWidget), matchesGoldenFile('goldens/widget_with_empty.png'));
    });

    testWidgets('Without items', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidget(
            widget: widget,
            items: null,
          ),
        ),
      );

      expect(find.text('No entries'), findsOneWidget);
      expect(find.byIcon(Icons.check), findsOneWidget);

      await expectLater(find.byType(DashboardWidget), matchesGoldenFile('goldens/widget_without_items.png'));
    });

    testWidgets('Without buttons', (final tester) async {
      await tester.pumpWidget(
        wrapWidget(
          accountsBloc,
          DashboardWidget(
            widget: widget.rebuild((final b) => b.buttons.clear()),
            items: items,
          ),
        ),
      );

      expect(find.bySubtype<FilledButton>(), findsNothing);

      await expectLater(find.byType(DashboardWidget), matchesGoldenFile('goldens/widget_without_buttons.png'));
    });
  });
}