// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/semantics_tester.dart';

const Key avatarA = Key('A');
const Key avatarC = Key('C');
const Key avatarD = Key('D');

Future<void> pumpTestWidget(
  WidgetTester tester, {
      bool withName = true,
      bool withEmail = true,
      bool withOnDetailsPressedHandler = true,
      Size otherAccountsPictureSize = const Size.square(40.0),
      Size currentAccountPictureSize  = const Size.square(72.0),
      Color? primaryColor,
      Color? colorSchemePrimary,
    }) async {
  await tester.pumpWidget(
    MaterialApp(
      theme: ThemeData(
        primaryColor: primaryColor,
        colorScheme: const ColorScheme.light().copyWith(primary: colorSchemePrimary),
      ),
      home: MediaQuery(
        data: const MediaQueryData(
          padding: EdgeInsets.only(
            left: 10.0,
            top: 20.0,
            right: 30.0,
            bottom: 40.0,
          ),
        ),
        child: Material(
          child: Center(
            child: UserAccountsDrawerHeader(
              onDetailsPressed: withOnDetailsPressedHandler ? () { } : null,
              currentAccountPictureSize: currentAccountPictureSize,
              otherAccountsPicturesSize: otherAccountsPictureSize,
              currentAccountPicture: const ExcludeSemantics(
                child: CircleAvatar(
                  key: avatarA,
                  child: Text('A'),
                ),
              ),
              otherAccountsPictures: const <Widget>[
                CircleAvatar(
                  child: Text('B'),
                ),
                CircleAvatar(
                  key: avatarC,
                  child: Text('C'),
                ),
                CircleAvatar(
                  key: avatarD,
                  child: Text('D'),
                ),
                CircleAvatar(
                  child: Text('E'),
                ),
              ],
              accountName: withName ? const Text('name') : null,
              accountEmail: withEmail ? const Text('email') : null,
            ),
          ),
        ),
      ),
    ),
  );
}

void main() {
  // Find the exact transform which is the descendant of [UserAccountsDrawerHeader].
  final Finder findTransform = find.descendant(
    of: find.byType(UserAccountsDrawerHeader),
    matching: find.byType(Transform),
  );

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader inherits ColorScheme.primary', (WidgetTester tester) async {
    const Color primaryColor = Color(0xff00ff00);
    const Color colorSchemePrimary = Color(0xff0000ff);

    await pumpTestWidget(tester, primaryColor: primaryColor, colorSchemePrimary: colorSchemePrimary);

    final BoxDecoration? boxDecoration = tester.widget<DrawerHeader>(
      find.byType(DrawerHeader),
    ).decoration as BoxDecoration?;
    expect(boxDecoration?.color == primaryColor, false);
    expect(boxDecoration?.color == colorSchemePrimary, true);
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader test', (WidgetTester tester) async {
    await pumpTestWidget(tester);

    expect(find.text('A'), findsOneWidget);
    expect(find.text('B'), findsOneWidget);
    expect(find.text('C'), findsOneWidget);
    expect(find.text('D'), findsOneWidget);
    expect(find.text('E'), findsNothing);

    expect(find.text('name'), findsOneWidget);
    expect(find.text('email'), findsOneWidget);

    RenderBox box = tester.renderObject(find.byKey(avatarA));
    expect(box.size.width, equals(72.0));
    expect(box.size.height, equals(72.0));

    box = tester.renderObject(find.byKey(avatarC));
    expect(box.size.width, equals(40.0));
    expect(box.size.height, equals(40.0));

    // Verify height = height + top padding + bottom margin + bottom edge)
    box = tester.renderObject(find.byType(UserAccountsDrawerHeader));
    expect(box.size.height, equals(160.0 + 20.0 + 8.0 + 1.0));

    final Offset topLeft = tester.getTopLeft(find.byType(UserAccountsDrawerHeader));
    final Offset topRight = tester.getTopRight(find.byType(UserAccountsDrawerHeader));

    final Offset avatarATopLeft = tester.getTopLeft(find.byKey(avatarA));
    final Offset avatarDTopRight = tester.getTopRight(find.byKey(avatarD));
    final Offset avatarCTopRight = tester.getTopRight(find.byKey(avatarC));

    expect(avatarATopLeft.dx - topLeft.dx, equals(16.0 + 10.0)); // left padding
    expect(avatarATopLeft.dy - topLeft.dy, equals(16.0 + 20.0)); // add top padding
    expect(topRight.dx - avatarDTopRight.dx, equals(16.0 + 30.0)); // right padding
    expect(avatarDTopRight.dy - topRight.dy, equals(16.0 + 20.0)); // add top padding
    expect(avatarDTopRight.dx - avatarCTopRight.dx, equals(40.0 + 16.0)); // size + space between
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader change default size test', (WidgetTester tester) async {
    const Size currentAccountPictureSize = Size.square(60.0);
    const Size otherAccountsPictureSize = Size.square(30.0);

    await pumpTestWidget(
      tester,
      currentAccountPictureSize: currentAccountPictureSize,
      otherAccountsPictureSize: otherAccountsPictureSize,
    );

    final RenderBox currentAccountRenderBox = tester.renderObject(find.byKey(avatarA));
    final RenderBox otherAccountRenderBox = tester.renderObject(find.byKey(avatarC));

    expect(currentAccountRenderBox.size, currentAccountPictureSize);
    expect(otherAccountRenderBox.size, otherAccountsPictureSize);
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader icon rotation test', (WidgetTester tester) async {
    await pumpTestWidget(tester);
    Transform transformWidget = tester.firstWidget(findTransform);

    // Icon is right side up.
    expect(transformWidget.transform.getRotation()[0], 1.0);
    expect(transformWidget.transform.getRotation()[4], 1.0);

    await tester.tap(find.byType(Icon));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    expect(tester.hasRunningAnimations, isTrue);

    await tester.pumpAndSettle();
    await tester.pump();
    transformWidget = tester.firstWidget(findTransform);

    // Icon has rotated 180 degrees.
    expect(transformWidget.transform.getRotation()[0], -1.0);
    expect(transformWidget.transform.getRotation()[4], -1.0);

    await tester.tap(find.byType(Icon));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    expect(tester.hasRunningAnimations, isTrue);

    await tester.pumpAndSettle();
    await tester.pump();
    transformWidget = tester.firstWidget(findTransform);

    // Icon has rotated 180 degrees back to the original position.
    expect(transformWidget.transform.getRotation()[0], 1.0);
    expect(transformWidget.transform.getRotation()[4], 1.0);
  });

  // Regression test for https://github.com/flutter/flutter/issues/25801.
  testWidgetsWithLeakTracking('UserAccountsDrawerHeader icon does not rotate after setState', (WidgetTester tester) async {
    late StateSetter testSetState;
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: StatefulBuilder(
          builder: (BuildContext context, StateSetter setState) {
            testSetState = setState;
            return UserAccountsDrawerHeader(
              onDetailsPressed: () { },
              accountName: const Text('name'),
              accountEmail: const Text('email'),
            );
          },
        ),
      ),
    ));

    Transform transformWidget = tester.firstWidget(findTransform);

    // Icon is right side up.
    expect(transformWidget.transform.getRotation()[0], 1.0);
    expect(transformWidget.transform.getRotation()[4], 1.0);

    testSetState(() { });
    await tester.pump(const Duration(milliseconds: 10));
    expect(tester.hasRunningAnimations, isFalse);

    expect(await tester.pumpAndSettle(), 1);
    transformWidget = tester.firstWidget(findTransform);

    // Icon has not rotated.
    expect(transformWidget.transform.getRotation()[0], 1.0);
    expect(transformWidget.transform.getRotation()[4], 1.0);
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader icon rotation test speeeeeedy', (WidgetTester tester) async {
    await pumpTestWidget(tester);
    Transform transformWidget = tester.firstWidget(findTransform);

    // Icon is right side up.
    expect(transformWidget.transform.getRotation()[0], 1.0);
    expect(transformWidget.transform.getRotation()[4], 1.0);

    // Icon starts to rotate down.
    await tester.tap(find.byType(Icon));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    expect(tester.hasRunningAnimations, isTrue);

    // Icon starts to rotate up mid animation.
    await tester.tap(find.byType(Icon));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    expect(tester.hasRunningAnimations, isTrue);

    // Icon starts to rotate down again still mid animation.
    await tester.tap(find.byType(Icon));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    expect(tester.hasRunningAnimations, isTrue);

    // Icon starts to rotate up to its original position mid animation.
    await tester.tap(find.byType(Icon));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    expect(tester.hasRunningAnimations, isTrue);

    await tester.pumpAndSettle();
    await tester.pump();
    transformWidget = tester.firstWidget(findTransform);

    // Icon has rotated 180 degrees back to the original position.
    expect(transformWidget.transform.getRotation()[0], 1.0);
    expect(transformWidget.transform.getRotation()[4], 1.0);
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader icon color changes', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: UserAccountsDrawerHeader(
          onDetailsPressed: () {},
          accountName: const Text('name'),
          accountEmail: const Text('email'),
        ),
      ),
    ));

    Icon iconWidget = tester.firstWidget(find.byType(Icon));
    // Default icon color is white.
    expect(iconWidget.color, Colors.white);

    const Color arrowColor = Colors.red;
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: UserAccountsDrawerHeader(
          onDetailsPressed: () { },
          accountName: const Text('name'),
          accountEmail: const Text('email'),
          arrowColor: arrowColor,
        ),
      ),
    ));

    iconWidget = tester.firstWidget(find.byType(Icon));
    expect(iconWidget.color, arrowColor);
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader null parameters LTR', (WidgetTester tester) async {
    Widget buildFrame({
      Widget? currentAccountPicture,
      List<Widget>? otherAccountsPictures,
      Widget? accountName,
      Widget? accountEmail,
      VoidCallback? onDetailsPressed,
      EdgeInsets? margin,
    }) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: UserAccountsDrawerHeader(
              currentAccountPicture: currentAccountPicture,
              otherAccountsPictures: otherAccountsPictures,
              accountName: accountName,
              accountEmail: accountEmail,
              onDetailsPressed: onDetailsPressed,
              margin: margin,
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame());
    final RenderBox box = tester.renderObject(find.byType(UserAccountsDrawerHeader));
    expect(box.size.height, equals(160.0 + 1.0)); // height + bottom edge)
    expect(find.byType(Icon), findsNothing);

    await tester.pumpWidget(buildFrame(
      onDetailsPressed: () { },
    ));
    expect(find.byType(Icon), findsOneWidget);

    await tester.pumpWidget(buildFrame(
      accountName: const Text('accountName'),
      onDetailsPressed: () { },
    ));
    expect(
      tester.getCenter(find.text('accountName')).dy,
      tester.getCenter(find.byType(Icon)).dy,
    );
    expect(
      tester.getCenter(find.text('accountName')).dx,
      lessThan(tester.getCenter(find.byType(Icon)).dx),
    );

    await tester.pumpWidget(buildFrame(
      accountEmail: const Text('accountEmail'),
      onDetailsPressed: () { },
    ));
    expect(
      tester.getCenter(find.text('accountEmail')).dy,
      tester.getCenter(find.byType(Icon)).dy,
    );
    expect(
      tester.getCenter(find.text('accountEmail')).dx,
      lessThan(tester.getCenter(find.byType(Icon)).dx),
    );

    await tester.pumpWidget(buildFrame(
      accountName: const Text('accountName'),
      accountEmail: const Text('accountEmail'),
      onDetailsPressed: () { },
    ));
    expect(
      tester.getCenter(find.text('accountEmail')).dy,
      tester.getCenter(find.byType(Icon)).dy,
    );
    expect(
      tester.getCenter(find.text('accountEmail')).dx,
      lessThan(tester.getCenter(find.byType(Icon)).dx),
    );
    expect(
      tester.getBottomLeft(find.text('accountEmail')).dy,
      greaterThan(tester.getBottomLeft(find.text('accountName')).dy),
    );
    expect(
      tester.getBottomLeft(find.text('accountEmail')).dx,
      tester.getBottomLeft(find.text('accountName')).dx,
    );

    await tester.pumpWidget(buildFrame(
      currentAccountPicture: const CircleAvatar(child: Text('A')),
    ));
    expect(find.text('A'), findsOneWidget);

    await tester.pumpWidget(buildFrame(
      otherAccountsPictures: <Widget>[const CircleAvatar(child: Text('A'))],
    ));
    expect(find.text('A'), findsOneWidget);

    const Key avatarA = Key('A');
    await tester.pumpWidget(buildFrame(
      currentAccountPicture: const CircleAvatar(key: avatarA, child: Text('A')),
      accountName: const Text('accountName'),
    ));
    expect(
      tester.getBottomLeft(find.byKey(avatarA)).dx,
      tester.getBottomLeft(find.text('accountName')).dx,
    );
    expect(
      tester.getBottomLeft(find.text('accountName')).dy,
      greaterThan(tester.getBottomLeft(find.byKey(avatarA)).dy),
    );
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader null parameters RTL', (WidgetTester tester) async {
    Widget buildFrame({
      Widget? currentAccountPicture,
      List<Widget>? otherAccountsPictures,
      Widget? accountName,
      Widget? accountEmail,
      VoidCallback? onDetailsPressed,
      EdgeInsets? margin,
    }) {
      return MaterialApp(
        home: Directionality(
          textDirection: TextDirection.rtl,
          child: Material(
            child: Center(
              child: UserAccountsDrawerHeader(
                currentAccountPicture: currentAccountPicture,
                otherAccountsPictures: otherAccountsPictures,
                accountName: accountName,
                accountEmail: accountEmail,
                onDetailsPressed: onDetailsPressed,
                margin: margin,
              ),
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame());
    final RenderBox box = tester.renderObject(find.byType(UserAccountsDrawerHeader));
    expect(box.size.height, equals(160.0 + 1.0)); // height + bottom edge)
    expect(find.byType(Icon), findsNothing);

    await tester.pumpWidget(buildFrame(
      onDetailsPressed: () { },
    ));
    expect(find.byType(Icon), findsOneWidget);

    await tester.pumpWidget(buildFrame(
      accountName: const Text('accountName'),
      onDetailsPressed: () { },
    ));
    expect(
      tester.getCenter(find.text('accountName')).dy,
      tester.getCenter(find.byType(Icon)).dy,
    );
    expect(
      tester.getCenter(find.text('accountName')).dx,
      greaterThan(tester.getCenter(find.byType(Icon)).dx),
    );

    await tester.pumpWidget(buildFrame(
      accountEmail: const Text('accountEmail'),
      onDetailsPressed: () { },
    ));
    expect(
      tester.getCenter(find.text('accountEmail')).dy,
      tester.getCenter(find.byType(Icon)).dy,
    );
    expect(
      tester.getCenter(find.text('accountEmail')).dx,
      greaterThan(tester.getCenter(find.byType(Icon)).dx),
    );

    await tester.pumpWidget(buildFrame(
      accountName: const Text('accountName'),
      accountEmail: const Text('accountEmail'),
      onDetailsPressed: () { },
    ));
    expect(
      tester.getCenter(find.text('accountEmail')).dy,
      tester.getCenter(find.byType(Icon)).dy,
    );
    expect(
      tester.getCenter(find.text('accountEmail')).dx,
      greaterThan(tester.getCenter(find.byType(Icon)).dx),
    );
    expect(
      tester.getBottomLeft(find.text('accountEmail')).dy,
      greaterThan(tester.getBottomLeft(find.text('accountName')).dy),
    );
    expect(
      tester.getBottomRight(find.text('accountEmail')).dx,
      tester.getBottomRight(find.text('accountName')).dx,
    );

    await tester.pumpWidget(buildFrame(
      currentAccountPicture: const CircleAvatar(child: Text('A')),
    ));
    expect(find.text('A'), findsOneWidget);

    await tester.pumpWidget(buildFrame(
      otherAccountsPictures: <Widget>[const CircleAvatar(child: Text('A'))],
    ));
    expect(find.text('A'), findsOneWidget);

    const Key avatarA = Key('A');
    await tester.pumpWidget(buildFrame(
      currentAccountPicture: const CircleAvatar(key: avatarA, child: Text('A')),
      accountName: const Text('accountName'),
    ));
    expect(
      tester.getBottomRight(find.byKey(avatarA)).dx,
      tester.getBottomRight(find.text('accountName')).dx,
    );
    expect(
      tester.getBottomLeft(find.text('accountName')).dy,
      greaterThan(tester.getBottomLeft(find.byKey(avatarA)).dy),
    );
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader provides semantics', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);
    await pumpTestWidget(tester);

    expect(
      semantics,
      hasSemantics(
        TestSemantics(
          children: <TestSemantics>[
            TestSemantics(
              children: <TestSemantics>[
                TestSemantics(
                  children: <TestSemantics>[
                    TestSemantics(
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
                          label: 'Signed in\nname\nemail',
                          textDirection: TextDirection.ltr,
                          children: <TestSemantics>[
                            TestSemantics(
                              label: r'B',
                              textDirection: TextDirection.ltr,
                            ),
                            TestSemantics(
                              label: r'C',
                              textDirection: TextDirection.ltr,
                            ),
                            TestSemantics(
                              label: r'D',
                              textDirection: TextDirection.ltr,
                            ),
                            TestSemantics(
                              flags: <SemanticsFlag>[SemanticsFlag.isButton],
                              actions: <SemanticsAction>[SemanticsAction.tap],
                              label: r'Show accounts',
                              textDirection: TextDirection.ltr,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreId: true, ignoreTransform: true, ignoreRect: true,
      ),
    );

    semantics.dispose();
  });

  testWidgetsWithLeakTracking('alternative account selectors have sufficient tap targets', (WidgetTester tester) async {
    final SemanticsHandle handle = tester.ensureSemantics();
    await pumpTestWidget(tester);

    expect(tester.getSemantics(find.text('B')), matchesSemantics(
      label: 'B',
      size: const Size(48.0, 48.0),
    ));

    expect(tester.getSemantics(find.text('C')), matchesSemantics(
      label: 'C',
      size: const Size(48.0, 48.0),
    ));

    expect(tester.getSemantics(find.text('D')), matchesSemantics(
      label: 'D',
      size: const Size(48.0, 48.0),
    ));
    handle.dispose();
  });

  testWidgetsWithLeakTracking('UserAccountsDrawerHeader provides semantics with missing properties', (WidgetTester tester) async {
    final SemanticsTester semantics = SemanticsTester(tester);
    await pumpTestWidget(
      tester,
      withEmail: false,
      withName: false,
      withOnDetailsPressedHandler: false,
    );

    expect(
      semantics,
      hasSemantics(
        TestSemantics(
          children: <TestSemantics>[
            TestSemantics(
              children: <TestSemantics>[
                TestSemantics(
                  children: <TestSemantics>[
                    TestSemantics(
                      flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
                      children: <TestSemantics>[
                        TestSemantics(
                          label: 'Signed in',
                          textDirection: TextDirection.ltr,
                          children: <TestSemantics>[
                            TestSemantics(
                              label: r'B',
                              textDirection: TextDirection.ltr,
                            ),
                            TestSemantics(
                              label: r'C',
                              textDirection: TextDirection.ltr,
                            ),
                            TestSemantics(
                              label: r'D',
                              textDirection: TextDirection.ltr,
                            ),
                          ],
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
        ignoreId: true, ignoreTransform: true, ignoreRect: true,
      ),
    );

    semantics.dispose();
  });
}
