// The view picker and the layout it drives.
//
// What the picker saves is a property of the car, read by the web app as well,
// so the two things worth guarding are that it offers exactly the keys the
// server accepts — with the date shown but locked on, since a hidden date is a
// 400 — and that a service card lays the columns out in the order it was given
// rather than the catalogue's.
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
import "package:intl/date_symbol_data_local.dart";
import "package:drivervault_phone/i18n.dart";
import "package:drivervault_phone/main.dart";
import "package:drivervault_phone/models.dart";
import "package:drivervault_phone/screens/car_view_sheet.dart";
import "package:drivervault_phone/service_parts.dart";

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  setUpAll(() async {
    await initializeDateFormatting();
    await loadTranslations();
    appSettings.locale = "en-GB";
    appSettings.dateFormat = "DMY_NUM";
  });

  group("serviceColumnRuns", () {
    test("the short columns share a line, the other three stand alone", () {
      expect(
        serviceColumnRuns(kServiceColumnKeys),
        [
          ["date", "km", "nextDate", "nextKm"],
          ["parts"],
          ["notes"],
          ["file"],
        ],
      );
    });

    test("a column moved between two short ones splits the line", () {
      // The grouping follows the car's arrangement, not the catalogue's: this is
      // the part a hand-written card would get wrong by grouping the short
      // columns first and laying the blocks out after them, which would quietly
      // undo the arrangement.
      expect(
        serviceColumnRuns(["date", "km", "notes", "nextDate", "nextKm"]),
        [
          ["date", "km"],
          ["notes"],
          ["nextDate", "nextKm"],
        ],
      );
    });

    test("hidden columns simply are not there", () {
      expect(
        serviceColumnRuns(["date", "notes"]),
        [
          ["date"],
          ["notes"],
        ],
      );
      // Date cannot be hidden, so a card always has at least one run — the tile
      // reads pieces.first without checking.
      expect(serviceColumnRuns(["date"]), [
        ["date"]
      ]);
    });
  });

  group("CarViewSheet", () {
    // A surface tall enough for the whole sheet. Its list is lazy, so on a
    // phone-sized one the columns — the last of the three sections — are simply
    // not built and nothing below can be found.
    Future<void> pump(WidgetTester tester, Car car) async {
      tester.view.physicalSize = const Size(800, 5000);
      tester.view.devicePixelRatio = 1.0;
      addTearDown(tester.view.reset);
      await tester.pumpWidget(MaterialApp(
        home: Scaffold(
          body: CarViewSheet(car: car, showProvider: false),
        ),
      ));
      await tester.pumpAndSettle();
    }

    Car car({
      List<String> hiddenColumns = const [],
      List<String> columnOrder = const [],
      List<String> hiddenParts = const [],
    }) =>
        Car.fromJson({
          "id": "car1",
          "name": "bZ4X",
          "hiddenServiceColumns": hiddenColumns,
          "serviceColumnOrder": columnOrder,
          "hiddenServiceParts": hiddenParts,
        });

    // The sheet stacks its sections in one list: tabs, Information rows, the
    // Service history columns, then the parts. Counted from the end rather than
    // the start so the tab rows — whose number depends on showProvider — don't
    // have to be worked out here.
    List<bool> ticks(WidgetTester tester) => tester
        .widgetList<Checkbox>(find.byType(Checkbox))
        .map((b) => b.value ?? false)
        .toList();
    List<bool> columnTicks(WidgetTester tester) {
      final all = ticks(tester);
      final end = all.length - kServiceParts.length;
      return all.sublist(end - kServiceColumnKeys.length, end);
    }
    List<bool> partTicks(WidgetTester tester) {
      final all = ticks(tester);
      return all.sublist(all.length - kServiceParts.length);
    }

    testWidgets("offers every column, with the date locked on", (tester) async {
      await pump(tester, car());

      // The section headings are rendered upper-cased.
      expect(find.text(t("car.viewPicker.serviceColumnsHeading").toUpperCase()),
          findsOneWidget);
      for (final key in kServiceColumnKeys) {
        expect(find.text(serviceColumnLabel(key)), findsWidgets,
            reason: "the $key column is not on offer");
      }

      // The date's checkbox is on and cannot be turned off; every other column's
      // can. A picker that let the date be unticked would save a hidden set the
      // server refuses.
      final boxes = tester.widgetList<Checkbox>(find.byType(Checkbox)).toList();
      final locked = boxes.where((b) => b.onChanged == null).toList();
      expect(locked, hasLength(1));
      expect(locked.single.value, isTrue);
    });

    testWidgets("a switched-off column comes back unticked", (tester) async {
      await pump(tester, car(hiddenColumns: ["parts", "file"]));

      expect(columnTicks(tester), [true, true, true, true, false, true, false]);
    });

    testWidgets("offers every part, none of them locked", (tester) async {
      await pump(tester, car());

      expect(find.text(t("car.viewPicker.servicePartsHeading").toUpperCase()),
          findsOneWidget);
      for (final part in kServiceParts) {
        expect(find.text(t(part.label)), findsWidgets,
            reason: "the ${part.key} part is not on offer");
      }
      expect(partTicks(tester), List.filled(kServiceParts.length, true));

      // Only the date is locked. Every part can be switched off: a service that
      // changed nothing is a real service, so the form needs none of them.
      final locked = tester
          .widgetList<Checkbox>(find.byType(Checkbox))
          .where((b) => b.onChanged == null);
      expect(locked, hasLength(1));
    });

    testWidgets("a switched-off part comes back unticked", (tester) async {
      await pump(tester, car(hiddenParts: ["oil", "cabinFilter"]));
      expect(partTicks(tester), [false, true, false]);
      // And it changes nothing about the columns, which are their own set.
      expect(columnTicks(tester), List.filled(kServiceColumnKeys.length, true));
    });

    testWidgets("the rows follow the car's arrangement", (tester) async {
      await pump(tester, car(columnOrder: ["notes", "date"]));

      final notes = tester.getTopLeft(find.text(serviceColumnLabel("notes")).last).dy;
      final date = tester.getTopLeft(find.text(serviceColumnLabel("date")).last).dy;
      final km = tester.getTopLeft(find.text(serviceColumnLabel("km")).last).dy;
      expect(notes, lessThan(date));
      expect(date, lessThan(km));
    });
  });
}
