import 'dart:convert';

import 'package:feature_inventory/feature_inventory.dart';
import 'package:feature_wordpress/feature_wordpress.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';

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

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

/// Builds a [MockClient] that returns [statusCode] and [body] for every request.
http.Client _mockClient(int statusCode, Map<String, dynamic> body) {
  return MockClient((_) async => http.Response(jsonEncode(body), statusCode));
}

WooCommerceApiClient _makeApiClient(http.Client httpClient) {
  return WooCommerceApiClient(
    siteUrl: 'https://test.example.com',
    consumerKey: 'ck_test',
    consumerSecret: 'cs_test',
    httpClient: httpClient,
  );
}

// ── Stock fields returned by WooCommerce product endpoint ──────────────────

Map<String, dynamic> _stockResponse({
  int id = 101,
  String sku = 'TEST-001',
  int? stockQuantity = 15,
  String stockStatus = 'instock',
  bool manageStock = true,
}) {
  return {
    'id': id,
    'sku': sku,
    'stock_quantity': stockQuantity,
    'stock_status': stockStatus,
    'manage_stock': manageStock,
  };
}

void main() {
  // ── InventorySyncStatus ────────────────────────────────────────────────────

  group('InventorySyncStatus', () {
    test('has five values', () {
      expect(InventorySyncStatus.values.length, 5);
    });

    test('contains expected statuses', () {
      expect(InventorySyncStatus.values, contains(InventorySyncStatus.notSynced));
      expect(InventorySyncStatus.values, contains(InventorySyncStatus.synced));
      expect(InventorySyncStatus.values, contains(InventorySyncStatus.pending));
      expect(InventorySyncStatus.values, contains(InventorySyncStatus.conflict));
      expect(InventorySyncStatus.values, contains(InventorySyncStatus.error));
    });
  });

  // ── InventoryItem sync fields ──────────────────────────────────────────────

  group('InventoryItem sync fields', () {
    test('defaults to notSynced when no wooCommerceProductId', () {
      final item = _makeItem();
      expect(item.wooCommerceProductId, isNull);
      expect(item.syncStatus, InventorySyncStatus.notSynced);
      expect(item.lastSyncedAt, isNull);
    });

    test('can be constructed with wooCommerceProductId and syncStatus', () {
      final now = DateTime(2026, 7, 1);
      final item = _makeItem(
        wooCommerceProductId: 101,
        syncStatus: InventorySyncStatus.synced,
        lastSyncedAt: now,
      );
      expect(item.wooCommerceProductId, 101);
      expect(item.syncStatus, InventorySyncStatus.synced);
      expect(item.lastSyncedAt, now);
    });

    test('copyWith preserves sync fields when not specified', () {
      final now = DateTime(2026, 7, 1);
      final item = _makeItem(
        wooCommerceProductId: 101,
        syncStatus: InventorySyncStatus.synced,
        lastSyncedAt: now,
      );
      final copy = item.copyWith(name: 'Updated');
      expect(copy.wooCommerceProductId, 101);
      expect(copy.syncStatus, InventorySyncStatus.synced);
      expect(copy.lastSyncedAt, now);
    });

    test('copyWith can update sync fields', () {
      final item = _makeItem(wooCommerceProductId: 101, syncStatus: InventorySyncStatus.pending);
      final now = DateTime(2026, 7, 11);
      final copy = item.copyWith(syncStatus: InventorySyncStatus.synced, lastSyncedAt: now);
      expect(copy.syncStatus, InventorySyncStatus.synced);
      expect(copy.lastSyncedAt, now);
    });
  });

  // ── FakeInventoryRepository — new methods ──────────────────────────────────

  group('FakeInventoryRepository — getItemBySku', () {
    late FakeInventoryRepository repo;

    setUp(() => repo = FakeInventoryRepository());

    test('returns item for known SKU', () async {
      final item = await repo.getItemBySku('BC-FLR-001');
      expect(item, isNotNull);
      expect(item!.sku, 'BC-FLR-001');
    });

    test('returns null for unknown SKU', () async {
      final item = await repo.getItemBySku('UNKNOWN-SKU');
      expect(item, isNull);
    });
  });

  group('FakeInventoryRepository — syncWithRemote', () {
    late FakeInventoryRepository repo;

    setUp(() => repo = FakeInventoryRepository());

    test('sets quantity to remote value', () async {
      final result = await repo.syncWithRemote('1', 25, 'WooCommerce sync');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 25);
    });

    test('updates syncStatus to synced', () async {
      final result = await repo.syncWithRemote('1', 10, 'sync');
      expect(result.updatedItem!.syncStatus, InventorySyncStatus.synced);
    });

    test('sets lastSyncedAt', () async {
      final before = DateTime.now();
      final result = await repo.syncWithRemote('1', 10, 'sync');
      expect(result.updatedItem!.lastSyncedAt, isNotNull);
      expect(result.updatedItem!.lastSyncedAt!.isAfter(before), true);
    });

    test('derives correct status after sync', () async {
      // Item 1: reorderPoint 5 — sync to 3 (below reorderPoint → lowStock)
      final result = await repo.syncWithRemote('1', 3, 'sync');
      expect(result.updatedItem!.status, InventoryStatus.lowStock);
    });

    test('derives outOfStock when synced to 0', () async {
      final result = await repo.syncWithRemote('1', 0, 'sync');
      expect(result.updatedItem!.status, InventoryStatus.outOfStock);
    });

    test('fails for unknown id', () async {
      final result = await repo.syncWithRemote('UNKNOWN', 10, 'sync');
      expect(result.success, false);
      expect(result.errorMessage, isNotEmpty);
    });

    test('fails for negative remote quantity', () async {
      final result = await repo.syncWithRemote('1', -1, 'sync');
      expect(result.success, false);
    });

    test('persists change across calls', () async {
      await repo.syncWithRemote('1', 42, 'sync');
      final items = await repo.getInventoryItems();
      final item = items.firstWhere((i) => i.id == '1');
      expect(item.quantityOnHand, 42);
    });

    test('seed data includes items with wooCommerceProductId', () async {
      final items = await repo.getInventoryItems();
      final withWcId = items.where((i) => i.wooCommerceProductId != null).toList();
      expect(withWcId, isNotEmpty);
    });

    test('seed data includes items without wooCommerceProductId', () async {
      final items = await repo.getInventoryItems();
      final withoutWcId = items.where((i) => i.wooCommerceProductId == null).toList();
      expect(withoutWcId, isNotEmpty);
    });
  });

  // ── WooCommerceApiClient — stock methods ───────────────────────────────────

  group('WooCommerceApiClient — getProductStock', () {
    test('returns stock fields on success', () async {
      final client = _makeApiClient(_mockClient(200, _stockResponse(stockQuantity: 15)));
      final result = await client.getProductStock('101');
      expect(result['id'], 101);
      expect(result['stock_quantity'], 15);
      expect(result['stock_status'], 'instock');
      expect(result['manage_stock'], true);
    });

    test('throws WooCommerceApiException on non-200', () async {
      final client = _makeApiClient(_mockClient(404, {'message': 'Not found'}));
      expect(() => client.getProductStock('999'), throwsA(isA<WooCommerceApiException>()));
    });

    test('handles null stock_quantity (unmanaged stock)', () async {
      final client = _makeApiClient(
        _mockClient(200, _stockResponse(stockQuantity: null, manageStock: false)),
      );
      final result = await client.getProductStock('101');
      expect(result['stock_quantity'], isNull);
      expect(result['manage_stock'], false);
    });
  });

  group('WooCommerceApiClient — updateProductStock', () {
    test('returns updated stock fields on success', () async {
      final client = _makeApiClient(_mockClient(200, _stockResponse(stockQuantity: 20)));
      final result = await client.updateProductStock('101', 20);
      expect(result['stock_quantity'], 20);
      expect(result['manage_stock'], true);
    });

    test('throws WooCommerceApiException on non-200', () async {
      final client = _makeApiClient(_mockClient(500, {'message': 'Server error'}));
      expect(() => client.updateProductStock('101', 5), throwsA(isA<WooCommerceApiException>()));
    });
  });

  group('WooCommerceApiClient — batchUpdateStock', () {
    test('returns list of updated stock maps on success', () async {
      final batchResponse = {
        'update': [
          _stockResponse(id: 101, stockQuantity: 10),
          _stockResponse(id: 102, stockQuantity: 5),
        ],
      };
      final client = _makeApiClient(_mockClient(200, batchResponse));
      final results = await client.batchUpdateStock([
        {'id': 101, 'stock_quantity': 10},
        {'id': 102, 'stock_quantity': 5},
      ]);
      expect(results.length, 2);
      expect(results[0]['id'], 101);
      expect(results[0]['stock_quantity'], 10);
      expect(results[1]['id'], 102);
      expect(results[1]['stock_quantity'], 5);
    });

    test('throws WooCommerceApiException on non-200', () async {
      final client = _makeApiClient(_mockClient(500, {'message': 'Server error'}));
      expect(
        () => client.batchUpdateStock([
          {'id': 101, 'stock_quantity': 10},
        ]),
        throwsA(isA<WooCommerceApiException>()),
      );
    });

    test('throws WooCommerceApiException when update array missing', () async {
      final client = _makeApiClient(_mockClient(200, {'create': [], 'delete': []}));
      expect(
        () => client.batchUpdateStock([
          {'id': 101, 'stock_quantity': 10},
        ]),
        throwsA(isA<WooCommerceApiException>()),
      );
    });
  });

  // ── WooCommerceInventoryRepository ────────────────────────────────────────

  group('WooCommerceInventoryRepository', () {
    late WooCommerceInventoryRepository repo;
    late InventoryItem itemWithWcId;
    late InventoryItem itemWithoutWcId;

    setUp(() {
      itemWithWcId = _makeItem(
        id: '1',
        sku: 'TEST-001',
        quantityOnHand: 10,
        reorderPoint: 3,
        wooCommerceProductId: 101,
        syncStatus: InventorySyncStatus.synced,
      );
      itemWithoutWcId = _makeItem(
        id: '2',
        sku: 'LOCAL-001',
        quantityOnHand: 5,
        reorderPoint: 2,
        syncStatus: InventorySyncStatus.notSynced,
      );
      repo = WooCommerceInventoryRepository(
        apiClient: _makeApiClient(_mockClient(200, _stockResponse())),
        initialItems: [itemWithWcId, itemWithoutWcId],
      );
    });

    test('getInventoryItems returns all items', () async {
      final items = await repo.getInventoryItems();
      expect(items.length, 2);
    });

    test('getItemBySku returns matching item', () async {
      final item = await repo.getItemBySku('TEST-001');
      expect(item, isNotNull);
      expect(item!.id, '1');
    });

    test('getItemBySku returns null for unknown SKU', () async {
      final item = await repo.getItemBySku('UNKNOWN');
      expect(item, isNull);
    });

    test('adjustQuantity pushes to WooCommerce for linked item', () async {
      final client = _makeApiClient(_mockClient(200, _stockResponse(stockQuantity: 15)));
      final wcRepo = WooCommerceInventoryRepository(
        apiClient: client,
        initialItems: [itemWithWcId],
      );
      final result = await wcRepo.adjustQuantity('1', 5, 'restock');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 15);
      expect(result.updatedItem!.syncStatus, InventorySyncStatus.synced);
      expect(result.updatedItem!.lastSyncedAt, isNotNull);
    });

    test('adjustQuantity does not push to WooCommerce for local-only item', () async {
      // No HTTP call expected — if it were made, the mock would return 200 anyway,
      // but syncStatus should remain notSynced.
      final result = await repo.adjustQuantity('2', 3, 'restock');
      expect(result.success, true);
      expect(result.updatedItem!.syncStatus, InventorySyncStatus.notSynced);
    });

    test('adjustQuantity fails when WooCommerce returns error', () async {
      final client = _makeApiClient(_mockClient(500, {'message': 'Server error'}));
      final wcRepo = WooCommerceInventoryRepository(
        apiClient: client,
        initialItems: [itemWithWcId],
      );
      final result = await wcRepo.adjustQuantity('1', 5, 'restock');
      expect(result.success, false);
      expect(result.errorMessage, contains('WooCommerce stock update failed'));
    });

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

    test('setQuantity pushes to WooCommerce for linked item', () async {
      final client = _makeApiClient(_mockClient(200, _stockResponse(stockQuantity: 50)));
      final wcRepo = WooCommerceInventoryRepository(
        apiClient: client,
        initialItems: [itemWithWcId],
      );
      final result = await wcRepo.setQuantity('1', 50, 'inventory count');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 50);
      expect(result.updatedItem!.syncStatus, InventorySyncStatus.synced);
    });

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

    test('syncWithRemote sets quantity and marks synced', () async {
      final result = await repo.syncWithRemote('1', 30, 'WooCommerce sync');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 30);
      expect(result.updatedItem!.syncStatus, InventorySyncStatus.synced);
      expect(result.updatedItem!.lastSyncedAt, isNotNull);
    });

    test('createItem adds item to repository', () async {
      final newItem = _makeItem(id: 'new-1', sku: 'NEW-001');
      await repo.createItem(newItem);
      final items = await repo.getInventoryItems();
      expect(items.length, 3);
    });

    test('updateItem updates existing item', () async {
      final updated = itemWithWcId.copyWith(name: 'Updated Name');
      final result = await repo.updateItem(updated);
      expect(result.name, 'Updated Name');
    });

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

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

  // ── InventorySyncService ───────────────────────────────────────────────────

  group('InventorySyncService', () {
    late FakeInventoryRepository repo;

    setUp(() => repo = FakeInventoryRepository());

    test('syncAll syncs items with wooCommerceProductId', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 20,
      );
      final result = await service.syncAll();
      expect(result.syncedCount, greaterThan(0));
      expect(result.allSucceeded, true);
    });

    test('syncAll skips items without wooCommerceProductId', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 20,
      );
      final result = await service.syncAll();
      // Seed data has 2 items without WC product ID (items 4 and 6)
      expect(result.skippedCount, 2);
    });

    test('syncAll updates local quantities to remote values', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 99,
      );
      await service.syncAll();
      final items = await repo.getInventoryItems();
      final synced = items.where((i) => i.wooCommerceProductId != null).toList();
      for (final item in synced) {
        expect(item.quantityOnHand, 99);
        expect(item.syncStatus, InventorySyncStatus.synced);
      }
    });

    test('syncAll records failure when fetchRemoteStock throws', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => throw Exception('network error'),
      );
      final result = await service.syncAll();
      expect(result.failedCount, greaterThan(0));
      expect(result.allSucceeded, false);
    });

    test('syncAll totalProcessed equals syncedCount + failedCount', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 10,
      );
      final result = await service.syncAll();
      expect(result.totalProcessed, result.syncedCount + result.failedCount);
    });

    test('syncItem syncs a single item by id', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 42,
      );
      // Item '1' has wooCommerceProductId 101
      final result = await service.syncItem('1');
      expect(result.success, true);
      expect(result.updatedItem!.quantityOnHand, 42);
    });

    test('syncItem returns failure for unknown id', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 10,
      );
      final result = await service.syncItem('UNKNOWN');
      expect(result.success, false);
    });

    test('syncItem returns failure for item without wooCommerceProductId', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 10,
      );
      // Item '4' has no wooCommerceProductId
      final result = await service.syncItem('4');
      expect(result.success, false);
      expect(result.errorMessage, contains('no WooCommerce product ID'));
    });

    test('checkDifference returns null for item without wooCommerceProductId', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 10,
      );
      final diff = await service.checkDifference('4');
      expect(diff, isNull);
    });

    test('checkDifference detects discrepancy', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 999,
      );
      // Item '1' has quantityOnHand 18, remote is 999
      final diff = await service.checkDifference('1');
      expect(diff, isNotNull);
      expect(diff!.hasDiscrepancy, true);
      expect(diff.localQuantity, 18);
      expect(diff.remoteQuantity, 999);
    });

    test('checkDifference reports in-sync when quantities match', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => 18, // matches item '1' seed qty
      );
      final diff = await service.checkDifference('1');
      expect(diff!.isInSync, true);
      expect(diff.hasDiscrepancy, false);
    });

    test('checkDifference reports error when fetchRemoteStock throws', () async {
      final service = InventorySyncService(
        inventoryRepository: repo,
        fetchRemoteStock: (_) async => throw Exception('timeout'),
      );
      final diff = await service.checkDifference('1');
      expect(diff, isNotNull);
      expect(diff!.syncStatus, InventorySyncStatus.error);
      expect(diff.errorMessage, isNotEmpty);
    });
  });

  // ── StockDifference ────────────────────────────────────────────────────────

  group('StockDifference', () {
    test('isInSync true when quantities match', () {
      const diff = StockDifference(
        itemId: '1',
        localQuantity: 10,
        remoteQuantity: 10,
        syncStatus: InventorySyncStatus.synced,
      );
      expect(diff.isInSync, true);
      expect(diff.hasDiscrepancy, false);
    });

    test('hasDiscrepancy true when quantities differ', () {
      const diff = StockDifference(
        itemId: '1',
        localQuantity: 10,
        remoteQuantity: 20,
        syncStatus: InventorySyncStatus.conflict,
      );
      expect(diff.hasDiscrepancy, true);
      expect(diff.isInSync, false);
    });

    test('isInSync false when remoteQuantity is null', () {
      const diff = StockDifference(
        itemId: '1',
        localQuantity: 10,
        remoteQuantity: null,
        syncStatus: InventorySyncStatus.error,
      );
      expect(diff.isInSync, false);
      expect(diff.hasDiscrepancy, false);
    });
  });

  // ── InventorySyncResult ────────────────────────────────────────────────────

  group('InventorySyncResult', () {
    test('allSucceeded true when failedCount is 0', () {
      const result = InventorySyncResult(
        syncedCount: 5,
        failedCount: 0,
        skippedCount: 1,
        itemResults: {},
      );
      expect(result.allSucceeded, true);
      expect(result.totalProcessed, 5);
    });

    test('allSucceeded false when failedCount > 0', () {
      const result = InventorySyncResult(
        syncedCount: 3,
        failedCount: 2,
        skippedCount: 0,
        itemResults: {},
      );
      expect(result.allSucceeded, false);
      expect(result.totalProcessed, 5);
    });
  });
}
