import 'package:feature_inventory/feature_inventory.dart';
import 'package:flutter_test/flutter_test.dart';

// ── Helpers ────────────────────────────────────────────────────────────────

InventoryController _makeController(FakeInventoryRepository repo) {
  return InventoryController(
    GetInventoryItems(repo),
    AdjustInventoryQuantity(repo),
    CreateInventoryItem(repo),
    UpdateInventoryItem(repo),
  );
}

InventoryItem _makeItem({
  String id = 'test-1',
  String sku = 'TEST-001',
  String name = 'Test Item',
  String description = 'A test item',
  String imageUrl = '',
  String category = 'Test Category',
  int quantityOnHand = 10,
  double unitPrice = 9.99,
  int reorderPoint = 3,
  String? supplier,
  String? location,
  InventoryStatus status = InventoryStatus.inStock,
  DateTime? lastUpdated,
}) {
  return InventoryItem(
    id: id,
    sku: sku,
    name: name,
    description: description,
    imageUrl: imageUrl,
    category: category,
    quantityOnHand: quantityOnHand,
    unitPrice: unitPrice,
    reorderPoint: reorderPoint,
    supplier: supplier,
    location: location,
    status: status,
    lastUpdated: lastUpdated ?? DateTime(2026, 7, 1),
  );
}

void main() {
  // ── InventoryStatus ────────────────────────────────────────────────────────

  group('InventoryStatus', () {
    test('has four values', () {
      expect(InventoryStatus.values.length, 4);
    });

    test('contains expected statuses', () {
      expect(InventoryStatus.values, contains(InventoryStatus.inStock));
      expect(InventoryStatus.values, contains(InventoryStatus.lowStock));
      expect(InventoryStatus.values, contains(InventoryStatus.outOfStock));
      expect(InventoryStatus.values, contains(InventoryStatus.draft));
    });
  });

  // ── InventoryItem ──────────────────────────────────────────────────────────

  group('InventoryItem', () {
    test('constructs with all required fields', () {
      final item = _makeItem();
      expect(item.id, 'test-1');
      expect(item.sku, 'TEST-001');
      expect(item.name, 'Test Item');
      expect(item.description, 'A test item');
      expect(item.imageUrl, '');
      expect(item.category, 'Test Category');
      expect(item.quantityOnHand, 10);
      expect(item.unitPrice, 9.99);
      expect(item.reorderPoint, 3);
      expect(item.supplier, isNull);
      expect(item.location, isNull);
      expect(item.status, InventoryStatus.inStock);
    });

    test('copyWith returns new instance with updated fields', () {
      final original = _makeItem();
      final copy = original.copyWith(name: 'Updated', quantityOnHand: 20);
      expect(copy.name, 'Updated');
      expect(copy.quantityOnHand, 20);
      // Unchanged fields preserved
      expect(copy.id, original.id);
      expect(copy.sku, original.sku);
      expect(copy.description, original.description);
      expect(copy.category, original.category);
      expect(copy.unitPrice, original.unitPrice);
      expect(copy.reorderPoint, original.reorderPoint);
      expect(copy.status, original.status);
    });

    test('copyWith preserves nullable fields when not specified', () {
      final item = _makeItem(supplier: 'Kell Creations', location: 'Shelf A1');
      final copy = item.copyWith(name: 'New Name');
      expect(copy.supplier, 'Kell Creations');
      expect(copy.location, 'Shelf A1');
    });

    test('copyWith can update nullable fields', () {
      final item = _makeItem();
      final copy = item.copyWith(supplier: 'New Supplier', location: 'Bin 3');
      expect(copy.supplier, 'New Supplier');
      expect(copy.location, 'Bin 3');
    });

    test('copyWith can update status', () {
      final item = _makeItem(status: InventoryStatus.inStock);
      final copy = item.copyWith(status: InventoryStatus.lowStock);
      expect(copy.status, InventoryStatus.lowStock);
    });

    test('copyWith can update imageUrl', () {
      final item = _makeItem(imageUrl: '');
      final copy = item.copyWith(imageUrl: 'https://example.com/img.jpg');
      expect(copy.imageUrl, 'https://example.com/img.jpg');
    });
  });

  // ── InventoryAdjustmentResult ──────────────────────────────────────────────

  group('InventoryAdjustmentResult', () {
    test('success result has correct fields', () {
      final item = _makeItem();
      final result = InventoryAdjustmentResult.success(item);
      expect(result.success, true);
      expect(result.updatedItem, item);
      expect(result.errorMessage, isNull);
    });

    test('failure result has correct fields', () {
      const result = InventoryAdjustmentResult.failure('Something went wrong');
      expect(result.success, false);
      expect(result.updatedItem, isNull);
      expect(result.errorMessage, 'Something went wrong');
    });
  });

  // ── FakeInventoryRepository ────────────────────────────────────────────────

  group('FakeInventoryRepository', () {
    late FakeInventoryRepository repository;

    setUp(() {
      repository = FakeInventoryRepository();
    });

    test('returns six sample items', () async {
      final items = await repository.getInventoryItems();
      expect(items.length, 6);
    });

    test('returns items with expected names', () async {
      final items = await repository.getInventoryItems();
      final names = items.map((i) => i.name).toList();
      expect(names, contains('Floral Bowl Cozy'));
      expect(names, contains('Citrus Coaster Set'));
      expect(names, contains('Ocean Nightlight'));
    });

    test('seed data includes expanded fields', () async {
      final items = await repository.getInventoryItems();
      final first = items.first;
      expect(first.description, isNotEmpty);
      expect(first.category, isNotEmpty);
      expect(first.reorderPoint, greaterThan(0));
      expect(first.lastUpdated, isNotNull);
    });

    test('seed data has items with supplier and location', () async {
      final items = await repository.getInventoryItems();
      final withSupplier = items.where((i) => i.supplier != null).toList();
      expect(withSupplier, isNotEmpty);
    });

    test('adjustQuantity adds stock successfully', () async {
      final result = await repository.adjustQuantity('1', 5, 'restock');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 23); // 18 + 5
    });

    test('adjustQuantity removes stock successfully', () async {
      final result = await repository.adjustQuantity('1', -3, 'sold');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 15); // 18 - 3
    });

    test('adjustQuantity fails when result would be negative', () async {
      final result = await repository.adjustQuantity('1', -100, 'error');
      expect(result.success, false);
      expect(result.errorMessage, isNotEmpty);
    });

    test('adjustQuantity fails for unknown id', () async {
      final result = await repository.adjustQuantity('UNKNOWN', 1, 'test');
      expect(result.success, false);
    });

    test('adjustQuantity updates status to outOfStock when qty reaches 0', () async {
      // Item 3 has qty 0 already; use item 4 (qty 23, reorderPoint 8)
      // Reduce to 0
      final result = await repository.adjustQuantity('4', -23, 'clear');
      expect(result.success, true);
      expect(result.updatedItem!.status, InventoryStatus.outOfStock);
    });

    test('adjustQuantity updates status to lowStock when qty at or below reorderPoint', () async {
      // Item 1: qty 18, reorderPoint 5 — reduce to 4 (below reorderPoint)
      final result = await repository.adjustQuantity('1', -14, 'reduce');
      expect(result.success, true);
      expect(result.updatedItem!.status, InventoryStatus.lowStock);
    });

    test('adjustQuantity updates status to inStock when qty above reorderPoint', () async {
      // Item 2: qty 7, reorderPoint 10 — add enough to go above reorderPoint
      final result = await repository.adjustQuantity('2', 10, 'restock');
      expect(result.success, true);
      expect(result.updatedItem!.status, InventoryStatus.inStock);
    });

    test('setQuantity sets absolute quantity', () async {
      final result = await repository.setQuantity('1', 50, 'inventory count');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 50);
    });

    test('setQuantity fails for negative quantity', () async {
      final result = await repository.setQuantity('1', -1, 'error');
      expect(result.success, false);
    });

    test('setQuantity fails for unknown id', () async {
      final result = await repository.setQuantity('UNKNOWN', 10, 'test');
      expect(result.success, false);
    });

    test('createItem adds item to repository', () async {
      final newItem = _makeItem(id: 'new-1', sku: 'NEW-001', name: 'New Item');
      await repository.createItem(newItem);
      final items = await repository.getInventoryItems();
      expect(items.length, 7);
      expect(items.any((i) => i.id == 'new-1'), true);
    });

    test('updateItem updates existing item', () async {
      final items = await repository.getInventoryItems();
      final original = items.first;
      final updated = original.copyWith(name: 'Updated Name', unitPrice: 99.99);
      final result = await repository.updateItem(updated);
      expect(result.name, 'Updated Name');
      expect(result.unitPrice, 99.99);
    });

    test('updateItem throws for unknown id', () async {
      final unknown = _makeItem(id: 'UNKNOWN');
      expect(() => repository.updateItem(unknown), throwsStateError);
    });

    test('updateItemImage updates image URL', () async {
      const newUrl = 'https://example.com/new-image.jpg';
      final result = await repository.updateItemImage('1', newUrl);
      expect(result.success, true);
      expect(result.updatedItem!.imageUrl, newUrl);
    });

    test('updateItemImage fails for unknown id', () async {
      final result = await repository.updateItemImage('UNKNOWN', 'url');
      expect(result.success, false);
    });

    test('adjustQuantity persists change across calls', () async {
      await repository.adjustQuantity('1', -5, 'sold');
      final items = await repository.getInventoryItems();
      final item = items.firstWhere((i) => i.id == '1');
      expect(item.quantityOnHand, 13); // 18 - 5
    });
  });

  // ── InventoryController ────────────────────────────────────────────────────

  group('InventoryController', () {
    late FakeInventoryRepository repository;
    late InventoryController controller;

    setUp(() {
      repository = FakeInventoryRepository();
      controller = _makeController(repository);
    });

    tearDown(() {
      controller.dispose();
    });

    test('starts with empty state', () {
      expect(controller.isLoading, false);
      expect(controller.isUpdating, false);
      expect(controller.items, isEmpty);
      expect(controller.selectedItem, isNull);
      expect(controller.activeFilter, isNull);
      expect(controller.searchQuery, '');
      expect(controller.error, isNull);
      expect(controller.lastActionResult, isNull);
    });

    test('load populates items', () async {
      await controller.load();

      expect(controller.isLoading, false);
      expect(controller.items.length, 6);
      expect(controller.error, isNull);
    });

    test('setFilter filters by status', () async {
      await controller.load();

      controller.setFilter('lowStock');
      expect(controller.items.length, 2);
      expect(controller.items.every((i) => i.status == InventoryStatus.lowStock), true);
    });

    test('setFilter with null shows all items', () async {
      await controller.load();

      controller.setFilter('lowStock');
      expect(controller.items.length, 2);

      controller.setFilter(null);
      expect(controller.items.length, 6);
    });

    test('setSearchQuery filters by name', () async {
      await controller.load();

      controller.setSearchQuery('cozy');
      expect(controller.items.length, 1);
      expect(controller.items.first.name, 'Floral Bowl Cozy');
    });

    test('setSearchQuery filters by SKU', () async {
      await controller.load();

      controller.setSearchQuery('BC-FLR');
      expect(controller.items.length, 1);
      expect(controller.items.first.sku, 'BC-FLR-001');
    });

    test('search is case-insensitive', () async {
      await controller.load();

      controller.setSearchQuery('OCEAN');
      expect(controller.items.length, 1);
      expect(controller.items.first.name, 'Ocean Nightlight');
    });

    test('filter and search combine', () async {
      await controller.load();

      controller.setFilter('inStock');
      controller.setSearchQuery('floral');
      expect(controller.items.length, 1);
      expect(controller.items.first.name, 'Floral Bowl Cozy');
    });

    test('selectItem sets selectedItem', () async {
      await controller.load();

      final item = controller.items[2];
      controller.selectItem(item);
      expect(controller.selectedItem, item);
    });

    test('selectBySku selects matching item', () async {
      await controller.load();

      final found = controller.selectBySku('CS-CIT-002');
      expect(found, true);
      expect(controller.selectedItem!.sku, 'CS-CIT-002');
    });

    test('selectBySku returns false for unknown SKU', () async {
      await controller.load();

      final found = controller.selectBySku('UNKNOWN');
      expect(found, false);
      expect(controller.selectedItem, isNull);
    });

    test('selection is cleared when filtered out', () async {
      await controller.load();

      // Select an inStock item.
      controller.selectBySku('BC-FLR-001');
      expect(controller.selectedItem, isNotNull);

      // Filter to lowStock — the selected item should be cleared.
      controller.setFilter('lowStock');
      expect(controller.selectedItem, isNull);
    });

    // ── Write action tests ─────────────────────────────────────────────────

    test('adjustQuantity succeeds and updates item in list', () async {
      await controller.load();

      await controller.adjustQuantity('1', 5, 'restock');

      expect(controller.isUpdating, false);
      expect(controller.lastActionResult, isNotNull);
      expect(controller.lastActionResult!.success, true);
      final item = controller.items.firstWhere((i) => i.id == '1');
      expect(item.quantityOnHand, 23); // 18 + 5
    });

    test('adjustQuantity failure sets lastActionResult with error', () async {
      await controller.load();

      await controller.adjustQuantity('1', -1000, 'error');

      expect(controller.lastActionResult!.success, false);
      expect(controller.lastActionResult!.errorMessage, isNotEmpty);
    });

    test('adjustQuantity refreshes selectedItem when it is the adjusted item', () async {
      await controller.load();
      controller.selectBySku('BC-FLR-001');
      expect(controller.selectedItem!.quantityOnHand, 18);

      await controller.adjustQuantity('1', 2, 'restock');

      expect(controller.selectedItem!.quantityOnHand, 20);
    });

    test('createItem adds item to list', () async {
      await controller.load();
      expect(controller.items.length, 6);

      final newItem = _makeItem(id: 'new-1', sku: 'NEW-001', name: 'New Item');
      await controller.createItem(newItem);

      expect(controller.items.length, 7);
      expect(controller.lastActionResult!.success, true);
    });

    test('updateItem updates item in list', () async {
      await controller.load();
      final original = controller.items.firstWhere((i) => i.id == '1');
      final updated = original.copyWith(name: 'Updated Name');

      await controller.updateItem(updated);

      expect(controller.lastActionResult!.success, true);
      final inList = controller.items.firstWhere((i) => i.id == '1');
      expect(inList.name, 'Updated Name');
    });

    test('updateItem refreshes selectedItem when it is the updated item', () async {
      await controller.load();
      controller.selectBySku('BC-FLR-001');
      final original = controller.selectedItem!;
      final updated = original.copyWith(name: 'Refreshed Name');

      await controller.updateItem(updated);

      expect(controller.selectedItem!.name, 'Refreshed Name');
    });

    test('isUpdating is true during write and false after', () async {
      await controller.load();

      final states = <bool>[];
      controller.addListener(() => states.add(controller.isUpdating));

      await controller.adjustQuantity('1', 1, 'test');

      // First notification: isUpdating = true, last: isUpdating = false
      expect(states.first, true);
      expect(states.last, false);
    });
  });
}
