import 'dart:convert';

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

import 'package:feature_wordpress/feature_wordpress.dart';

// ── Shared JSON builders ──────────────────────────────────────────────────

Map<String, dynamic> _buildProductJson({
  int id = 1,
  String name = 'Test Product',
  String status = 'draft',
  String price = '9.99',
  String sku = 'SKU-1',
  String description = '<p>A product</p>',
  List<Map<String, dynamic>>? categories,
  List<Map<String, dynamic>>? images,
}) {
  return {
    'id': id,
    'name': name,
    'description': description,
    'price': price,
    'regular_price': price,
    'sku': sku,
    'status': status,
    'date_modified': '2026-07-01T10:00:00',
    'date_created': '2026-07-01T08:00:00',
    'categories':
        categories ??
        [
          {'id': 1, 'name': 'Test Category', 'slug': 'test-category'},
        ],
    'images': images ?? [],
  };
}

Map<String, dynamic> _buildCategoryJson({
  int id = 1,
  String name = 'Test Category',
  String slug = 'test-category',
  int parent = 0,
  int count = 0,
}) {
  return {'id': id, 'name': name, 'slug': slug, 'parent': parent, 'count': count};
}

WooCommerceApiClient _buildClient(MockClient mockClient) {
  return WooCommerceApiClient(
    siteUrl: 'https://store.example.com',
    consumerKey: 'ck_test',
    consumerSecret: 'cs_test',
    httpClient: mockClient,
  );
}

// ── ProductCategory domain model ──────────────────────────────────────────

void main() {
  group('ProductCategory', () {
    test('constructs with all fields', () {
      const cat = ProductCategory(
        id: 1,
        name: 'Bowl Cozies',
        slug: 'bowl-cozies',
        parentId: 0,
        count: 5,
      );

      expect(cat.id, 1);
      expect(cat.name, 'Bowl Cozies');
      expect(cat.slug, 'bowl-cozies');
      expect(cat.parentId, 0);
      expect(cat.count, 5);
    });

    test('copyWith replaces specified fields', () {
      const original = ProductCategory(
        id: 1,
        name: 'Bowl Cozies',
        slug: 'bowl-cozies',
        parentId: 0,
        count: 5,
      );

      final updated = original.copyWith(name: 'Updated Cozies', count: 10);

      expect(updated.id, 1);
      expect(updated.name, 'Updated Cozies');
      expect(updated.slug, 'bowl-cozies');
      expect(updated.parentId, 0);
      expect(updated.count, 10);
    });

    test('equality holds for identical values', () {
      const a = ProductCategory(id: 1, name: 'A', slug: 'a', parentId: 0, count: 0);
      const b = ProductCategory(id: 1, name: 'A', slug: 'a', parentId: 0, count: 0);
      expect(a, equals(b));
      expect(a.hashCode, equals(b.hashCode));
    });

    test('equality fails for different id', () {
      const a = ProductCategory(id: 1, name: 'A', slug: 'a', parentId: 0, count: 0);
      const b = ProductCategory(id: 2, name: 'A', slug: 'a', parentId: 0, count: 0);
      expect(a, isNot(equals(b)));
    });

    test('toString includes id, name, slug', () {
      const cat = ProductCategory(id: 3, name: 'Coasters', slug: 'coasters', parentId: 0, count: 2);
      expect(cat.toString(), contains('3'));
      expect(cat.toString(), contains('Coasters'));
      expect(cat.toString(), contains('coasters'));
    });
  });

  // ── ProductImage domain model ─────────────────────────────────────────────

  group('ProductImage', () {
    test('constructs with all fields', () {
      const img = ProductImage(
        id: 10,
        url: 'https://example.com/img.jpg',
        alt: 'A product image',
        position: 0,
      );

      expect(img.id, 10);
      expect(img.url, 'https://example.com/img.jpg');
      expect(img.alt, 'A product image');
      expect(img.position, 0);
    });

    test('copyWith replaces specified fields', () {
      const original = ProductImage(id: 1, url: 'https://a.com/1.jpg', alt: '', position: 0);
      final updated = original.copyWith(alt: 'New alt', position: 1);

      expect(updated.id, 1);
      expect(updated.url, 'https://a.com/1.jpg');
      expect(updated.alt, 'New alt');
      expect(updated.position, 1);
    });

    test('equality holds for identical values', () {
      const a = ProductImage(id: 1, url: 'https://a.com/1.jpg', alt: '', position: 0);
      const b = ProductImage(id: 1, url: 'https://a.com/1.jpg', alt: '', position: 0);
      expect(a, equals(b));
      expect(a.hashCode, equals(b.hashCode));
    });

    test('equality fails for different id', () {
      const a = ProductImage(id: 1, url: 'https://a.com/1.jpg', alt: '', position: 0);
      const b = ProductImage(id: 2, url: 'https://a.com/1.jpg', alt: '', position: 0);
      expect(a, isNot(equals(b)));
    });
  });

  // ── FakeProductPublishingRepository — catalog expansion ───────────────────

  group('FakeProductPublishingRepository — Stage 9C', () {
    late FakeProductPublishingRepository repo;

    setUp(() => repo = FakeProductPublishingRepository());

    // createProduct
    test('createProduct adds a new draft to the list', () async {
      final before = await repo.getProductDrafts();
      final created = await repo.createProduct(name: 'New Bowl Cozy', price: 14.99);

      expect(created.name, 'New Bowl Cozy');
      expect(created.price, 14.99);
      expect(created.status, PublishStatus.draft);
      expect(created.category, 'Uncategorized');

      final after = await repo.getProductDrafts();
      expect(after.length, before.length + 1);
      expect(after.any((d) => d.id == created.id), isTrue);
    });

    test('createProduct with categoryId resolves category name', () async {
      // Category id=1 is 'Bowl Cozies' in seed data.
      final created = await repo.createProduct(name: 'New Cozy', price: 9.99, categoryId: 1);
      expect(created.category, 'Bowl Cozies');
    });

    test('createProduct with description and sku stores them', () async {
      final created = await repo.createProduct(
        name: 'Fancy Cozy',
        price: 19.99,
        description: 'A fancy cozy',
        sku: 'FC-001',
      );
      expect(created.description, 'A fancy cozy');
      expect(created.sku, 'FC-001');
    });

    test('createProduct with custom status stores it', () async {
      final created = await repo.createProduct(
        name: 'Published Cozy',
        price: 12.99,
        status: PublishStatus.published,
      );
      expect(created.status, PublishStatus.published);
    });

    test('createProduct assigns unique incrementing IDs', () async {
      final a = await repo.createProduct(name: 'A', price: 1.0);
      final b = await repo.createProduct(name: 'B', price: 2.0);
      expect(a.id, isNot(equals(b.id)));
    });

    // deleteProduct
    test('deleteProduct removes the product from the list', () async {
      final before = await repo.getProductDrafts();
      await repo.deleteProduct('1');
      final after = await repo.getProductDrafts();

      expect(after.length, before.length - 1);
      expect(after.any((d) => d.id == '1'), isFalse);
    });

    test('deleteProduct throws StateError for unknown id', () async {
      expect(() => repo.deleteProduct('999'), throwsA(isA<StateError>()));
    });

    // updateProductImages
    test('updateProductImages sets primary imageUrl on draft', () async {
      final updated = await repo.updateProductImages('1', [101, 102]);

      expect(updated.id, '1');
      expect(updated.imageUrl, contains('101'));
    });

    test('updateProductImages with empty list clears imageUrl', () async {
      // First set some images.
      await repo.updateProductImages('1', [101]);
      // Then clear them.
      final updated = await repo.updateProductImages('1', []);
      expect(updated.imageUrl, '');
    });

    test('updateProductImages throws StateError for unknown id', () async {
      expect(() => repo.updateProductImages('999', [1]), throwsA(isA<StateError>()));
    });

    // getProductImages
    test('getProductImages returns empty list before any images set', () async {
      final images = await repo.getProductImages('1');
      expect(images, isEmpty);
    });

    test('getProductImages returns images after updateProductImages', () async {
      await repo.updateProductImages('1', [101, 102]);
      final images = await repo.getProductImages('1');

      expect(images.length, 2);
      expect(images[0].id, 101);
      expect(images[0].position, 0);
      expect(images[1].id, 102);
      expect(images[1].position, 1);
    });

    // getCategories
    test('getCategories returns seed categories', () async {
      final cats = await repo.getCategories();
      expect(cats.length, 4);
      expect(
        cats.map((c) => c.name),
        containsAll(['Bowl Cozies', 'Coasters', 'Nightlights', 'Kitchen Accessories']),
      );
    });

    // createCategory
    test('createCategory adds a new category', () async {
      final before = await repo.getCategories();
      final created = await repo.createCategory(name: 'Pot Holders');

      expect(created.name, 'Pot Holders');
      expect(created.slug, 'pot-holders');
      expect(created.parentId, 0);
      expect(created.count, 0);

      final after = await repo.getCategories();
      expect(after.length, before.length + 1);
    });

    test('createCategory with parentId stores it', () async {
      final created = await repo.createCategory(name: 'Sub Category', parentId: 1);
      expect(created.parentId, 1);
    });

    test('createCategory generates slug from name', () async {
      final created = await repo.createCategory(name: 'My New Category');
      expect(created.slug, 'my-new-category');
    });

    // updateCategory
    test('updateCategory updates name and slug', () async {
      final updated = await repo.updateCategory(1, name: 'Updated Bowl Cozies');
      expect(updated.name, 'Updated Bowl Cozies');
      expect(updated.slug, 'updated-bowl-cozies');
    });

    test('updateCategory with parentId updates it', () async {
      final updated = await repo.updateCategory(2, parentId: 1);
      expect(updated.parentId, 1);
    });

    test('updateCategory throws StateError for unknown id', () async {
      expect(() => repo.updateCategory(999, name: 'X'), throwsA(isA<StateError>()));
    });

    // deleteCategory
    test('deleteCategory removes the category', () async {
      final before = await repo.getCategories();
      await repo.deleteCategory(1);
      final after = await repo.getCategories();

      expect(after.length, before.length - 1);
      expect(after.any((c) => c.id == 1), isFalse);
    });

    test('deleteCategory throws StateError for unknown id', () async {
      expect(() => repo.deleteCategory(999), throwsA(isA<StateError>()));
    });
  });

  // ── WooCommerceApiClient — catalog expansion ──────────────────────────────

  group('WooCommerceApiClient — Stage 9C', () {
    // createProduct
    test('createProduct sends POST to /products and returns JSON on 201', () async {
      Map<String, dynamic>? capturedBody;
      String? capturedMethod;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedMethod = request.method;
        capturedUri = request.url;
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(jsonEncode(_buildProductJson(id: 99, name: 'New Product')), 201);
      });

      final client = _buildClient(mockClient);
      final result = await client.createProduct({'name': 'New Product', 'regular_price': '9.99'});

      expect(capturedMethod, 'POST');
      expect(capturedUri!.path, '/wp-json/wc/v3/products');
      expect(capturedBody!['name'], 'New Product');
      expect(result['id'], 99);
    });

    test('createProduct throws WooCommerceApiException on non-201', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"rest_invalid_param"}', 400);
      });

      final client = _buildClient(mockClient);
      expect(() => client.createProduct({'name': ''}), throwsA(isA<WooCommerceApiException>()));
    });

    // deleteProduct
    test('deleteProduct sends DELETE to /products/{id} with force param', () async {
      String? capturedMethod;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedMethod = request.method;
        capturedUri = request.url;
        return http.Response(jsonEncode(_buildProductJson(id: 5)), 200);
      });

      final client = _buildClient(mockClient);
      await client.deleteProduct('5', force: true);

      expect(capturedMethod, 'DELETE');
      expect(capturedUri!.path, '/wp-json/wc/v3/products/5');
      expect(capturedUri!.queryParameters['force'], 'true');
    });

    test('deleteProduct throws WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"rest_cannot_delete"}', 403);
      });

      final client = _buildClient(mockClient);
      expect(() => client.deleteProduct('5'), throwsA(isA<WooCommerceApiException>()));
    });

    // getCategories
    test('getCategories fetches all categories with pagination', () async {
      var requestCount = 0;
      final mockClient = MockClient((request) async {
        requestCount++;
        final page = int.parse(request.url.queryParameters['page']!);
        final perPage = int.parse(request.url.queryParameters['per_page']!);
        // Page 1 returns perPage items, page 2 returns 1 (short page).
        final count = page == 1 ? perPage : 1;
        final cats = List.generate(
          count,
          (i) => _buildCategoryJson(id: i + 1, name: 'Cat ${i + 1}'),
        );
        return http.Response(jsonEncode(cats), 200);
      });

      final client = _buildClient(mockClient);
      final cats = await client.getCategories(perPage: 2);

      expect(cats.length, 3); // 2 + 1
      expect(requestCount, 2);
    });

    test('getCategories throws WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('Server Error', 500);
      });

      final client = _buildClient(mockClient);
      expect(() => client.getCategories(), throwsA(isA<WooCommerceApiException>()));
    });

    // createCategory
    test('createCategory sends POST to /products/categories and returns JSON on 201', () async {
      Map<String, dynamic>? capturedBody;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedUri = request.url;
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(_buildCategoryJson(id: 10, name: 'Pot Holders', slug: 'pot-holders')),
          201,
        );
      });

      final client = _buildClient(mockClient);
      final result = await client.createCategory({'name': 'Pot Holders'});

      expect(capturedUri!.path, '/wp-json/wc/v3/products/categories');
      expect(capturedBody!['name'], 'Pot Holders');
      expect(result['id'], 10);
      expect(result['name'], 'Pot Holders');
    });

    test('createCategory throws WooCommerceApiException on non-201', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"term_exists"}', 400);
      });

      final client = _buildClient(mockClient);
      expect(
        () => client.createCategory({'name': 'Existing'}),
        throwsA(isA<WooCommerceApiException>()),
      );
    });

    // updateCategory
    test('updateCategory sends PUT to /products/categories/{id}', () async {
      String? capturedMethod;
      Uri? capturedUri;
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedMethod = request.method;
        capturedUri = request.url;
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(_buildCategoryJson(id: 3, name: 'Updated Name', slug: 'updated-name')),
          200,
        );
      });

      final client = _buildClient(mockClient);
      final result = await client.updateCategory(3, {'name': 'Updated Name'});

      expect(capturedMethod, 'PUT');
      expect(capturedUri!.path, '/wp-json/wc/v3/products/categories/3');
      expect(capturedBody!['name'], 'Updated Name');
      expect(result['name'], 'Updated Name');
    });

    test('updateCategory throws WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"rest_cannot_edit"}', 403);
      });

      final client = _buildClient(mockClient);
      expect(
        () => client.updateCategory(3, {'name': 'X'}),
        throwsA(isA<WooCommerceApiException>()),
      );
    });

    // deleteCategory
    test('deleteCategory sends DELETE to /products/categories/{id} with force', () async {
      String? capturedMethod;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedMethod = request.method;
        capturedUri = request.url;
        return http.Response(jsonEncode(_buildCategoryJson(id: 2)), 200);
      });

      final client = _buildClient(mockClient);
      await client.deleteCategory(2, force: true);

      expect(capturedMethod, 'DELETE');
      expect(capturedUri!.path, '/wp-json/wc/v3/products/categories/2');
      expect(capturedUri!.queryParameters['force'], 'true');
    });

    test('deleteCategory throws WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"rest_cannot_delete"}', 403);
      });

      final client = _buildClient(mockClient);
      expect(() => client.deleteCategory(2), throwsA(isA<WooCommerceApiException>()));
    });

    // getProductImages
    test('getProductImages returns images array from product JSON', () async {
      final mockClient = MockClient((request) async {
        return http.Response(
          jsonEncode(
            _buildProductJson(
              id: 1,
              images: [
                {'id': 10, 'src': 'https://example.com/img1.jpg', 'alt': 'Image 1'},
                {'id': 11, 'src': 'https://example.com/img2.jpg', 'alt': 'Image 2'},
              ],
            ),
          ),
          200,
        );
      });

      final client = _buildClient(mockClient);
      final images = await client.getProductImages('1');

      expect(images.length, 2);
      expect(images[0]['id'], 10);
      expect(images[0]['src'], 'https://example.com/img1.jpg');
    });

    test('getProductImages returns empty list when images field is absent', () async {
      final mockClient = MockClient((request) async {
        final json = _buildProductJson(id: 1);
        json.remove('images');
        return http.Response(jsonEncode(json), 200);
      });

      final client = _buildClient(mockClient);
      final images = await client.getProductImages('1');
      expect(images, isEmpty);
    });

    test('getProductImages throws WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('Not Found', 404);
      });

      final client = _buildClient(mockClient);
      expect(() => client.getProductImages('1'), throwsA(isA<WooCommerceApiException>()));
    });

    // setProductImages
    test('setProductImages sends PUT with images array', () async {
      Map<String, dynamic>? capturedBody;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedUri = request.url;
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(
            _buildProductJson(
              id: 7,
              images: [
                {'id': 101, 'src': 'https://example.com/101.jpg', 'alt': ''},
              ],
            ),
          ),
          200,
        );
      });

      final client = _buildClient(mockClient);
      final result = await client.setProductImages('7', [101]);

      expect(capturedUri!.path, '/wp-json/wc/v3/products/7');
      expect(capturedBody!['images'], [
        {'id': 101},
      ]);
      expect(result['id'], 7);
    });

    test('setProductImages with empty list sends empty images array', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(jsonEncode(_buildProductJson(id: 7, images: [])), 200);
      });

      final client = _buildClient(mockClient);
      await client.setProductImages('7', []);

      expect(capturedBody!['images'], isEmpty);
    });

    test('setProductImages throws WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"rest_cannot_edit"}', 403);
      });

      final client = _buildClient(mockClient);
      expect(() => client.setProductImages('7', [101]), throwsA(isA<WooCommerceApiException>()));
    });
  });

  // ── WordPressProductPublishingRepository — catalog expansion ──────────────

  group('WordPressProductPublishingRepository — Stage 9C', () {
    WordPressProductPublishingRepository buildRepo(MockClient mockClient) {
      return WordPressProductPublishingRepository(apiClient: _buildClient(mockClient));
    }

    // createProduct
    test('createProduct sends POST with correct fields and returns ProductDraft', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(_buildProductJson(id: 99, name: 'New Cozy', status: 'draft', price: '14.99')),
          201,
        );
      });

      final repo = buildRepo(mockClient);
      final result = await repo.createProduct(name: 'New Cozy', price: 14.99);

      expect(capturedBody!['name'], 'New Cozy');
      expect(capturedBody!['regular_price'], '14.99');
      expect(capturedBody!['status'], 'draft');
      expect(result, isA<ProductDraft>());
      expect(result.id, '99');
      expect(result.name, 'New Cozy');
      expect(result.status, PublishStatus.draft);
    });

    test('createProduct with description and sku includes them in request', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(_buildProductJson(id: 10, name: 'Fancy', sku: 'FC-001')),
          201,
        );
      });

      final repo = buildRepo(mockClient);
      await repo.createProduct(
        name: 'Fancy',
        price: 9.99,
        description: 'A fancy product',
        sku: 'FC-001',
      );

      expect(capturedBody!['description'], 'A fancy product');
      expect(capturedBody!['sku'], 'FC-001');
    });

    test('createProduct with categoryId includes categories in request', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(jsonEncode(_buildProductJson(id: 11)), 201);
      });

      final repo = buildRepo(mockClient);
      await repo.createProduct(name: 'Cat Product', price: 5.0, categoryId: 3);

      expect(capturedBody!['categories'], [
        {'id': 3},
      ]);
    });

    test('createProduct with published status sends publish', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(jsonEncode(_buildProductJson(id: 12, status: 'publish')), 201);
      });

      final repo = buildRepo(mockClient);
      await repo.createProduct(name: 'Published', price: 9.99, status: PublishStatus.published);

      expect(capturedBody!['status'], 'publish');
    });

    test('createProduct propagates WooCommerceApiException on non-201', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"rest_invalid_param"}', 400);
      });

      final repo = buildRepo(mockClient);
      expect(
        () => repo.createProduct(name: 'X', price: 1.0),
        throwsA(isA<WooCommerceApiException>()),
      );
    });

    // deleteProduct
    test('deleteProduct sends DELETE and completes without error', () async {
      String? capturedMethod;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedMethod = request.method;
        capturedUri = request.url;
        return http.Response(jsonEncode(_buildProductJson(id: 5)), 200);
      });

      final repo = buildRepo(mockClient);
      await repo.deleteProduct('5', force: true);

      expect(capturedMethod, 'DELETE');
      expect(capturedUri!.path, '/wp-json/wc/v3/products/5');
      expect(capturedUri!.queryParameters['force'], 'true');
    });

    test('deleteProduct propagates WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('{"code":"rest_cannot_delete"}', 403);
      });

      final repo = buildRepo(mockClient);
      expect(() => repo.deleteProduct('5'), throwsA(isA<WooCommerceApiException>()));
    });

    // updateProductImages
    test('updateProductImages sends PUT with images and returns updated ProductDraft', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(
            _buildProductJson(
              id: 1,
              images: [
                {'id': 101, 'src': 'https://example.com/101.jpg', 'alt': ''},
              ],
            ),
          ),
          200,
        );
      });

      final repo = buildRepo(mockClient);
      final result = await repo.updateProductImages('1', [101]);

      expect(capturedBody!['images'], [
        {'id': 101},
      ]);
      expect(result, isA<ProductDraft>());
      expect(result.imageUrl, 'https://example.com/101.jpg');
    });

    // getCategories
    test('getCategories returns mapped ProductCategory list', () async {
      final mockClient = MockClient((request) async {
        return http.Response(
          jsonEncode([
            _buildCategoryJson(id: 1, name: 'Bowl Cozies', slug: 'bowl-cozies', count: 3),
            _buildCategoryJson(id: 2, name: 'Coasters', slug: 'coasters', count: 5),
          ]),
          200,
        );
      });

      final repo = buildRepo(mockClient);
      final cats = await repo.getCategories();

      expect(cats.length, 2);
      expect(cats[0], isA<ProductCategory>());
      expect(cats[0].id, 1);
      expect(cats[0].name, 'Bowl Cozies');
      expect(cats[0].slug, 'bowl-cozies');
      expect(cats[0].count, 3);
      expect(cats[1].id, 2);
    });

    test('getCategories propagates WooCommerceApiException on non-200', () async {
      final mockClient = MockClient((request) async {
        return http.Response('Server Error', 500);
      });

      final repo = buildRepo(mockClient);
      expect(() => repo.getCategories(), throwsA(isA<WooCommerceApiException>()));
    });

    // createCategory
    test('createCategory sends POST with name and returns ProductCategory', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(_buildCategoryJson(id: 10, name: 'Pot Holders', slug: 'pot-holders')),
          201,
        );
      });

      final repo = buildRepo(mockClient);
      final result = await repo.createCategory(name: 'Pot Holders');

      expect(capturedBody!['name'], 'Pot Holders');
      expect(capturedBody!.containsKey('parent'), isFalse); // parentId=0 omitted
      expect(result, isA<ProductCategory>());
      expect(result.id, 10);
      expect(result.name, 'Pot Holders');
    });

    test('createCategory with parentId includes parent in request', () async {
      Map<String, dynamic>? capturedBody;

      final mockClient = MockClient((request) async {
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(_buildCategoryJson(id: 11, name: 'Sub', slug: 'sub', parent: 1)),
          201,
        );
      });

      final repo = buildRepo(mockClient);
      final result = await repo.createCategory(name: 'Sub', parentId: 1);

      expect(capturedBody!['parent'], 1);
      expect(result.parentId, 1);
    });

    // updateCategory
    test('updateCategory sends PUT with name and returns updated ProductCategory', () async {
      Map<String, dynamic>? capturedBody;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedUri = request.url;
        capturedBody = jsonDecode(request.body) as Map<String, dynamic>;
        return http.Response(
          jsonEncode(_buildCategoryJson(id: 3, name: 'Updated', slug: 'updated')),
          200,
        );
      });

      final repo = buildRepo(mockClient);
      final result = await repo.updateCategory(3, name: 'Updated');

      expect(capturedUri!.path, '/wp-json/wc/v3/products/categories/3');
      expect(capturedBody!['name'], 'Updated');
      expect(result.name, 'Updated');
    });

    // deleteCategory
    test('deleteCategory sends DELETE and completes without error', () async {
      String? capturedMethod;
      Uri? capturedUri;

      final mockClient = MockClient((request) async {
        capturedMethod = request.method;
        capturedUri = request.url;
        return http.Response(jsonEncode(_buildCategoryJson(id: 2)), 200);
      });

      final repo = buildRepo(mockClient);
      await repo.deleteCategory(2);

      expect(capturedMethod, 'DELETE');
      expect(capturedUri!.path, '/wp-json/wc/v3/products/categories/2');
    });

    // getProductImages
    test('getProductImages returns mapped ProductImage list', () async {
      final mockClient = MockClient((request) async {
        return http.Response(
          jsonEncode(
            _buildProductJson(
              id: 1,
              images: [
                {'id': 10, 'src': 'https://example.com/img1.jpg', 'alt': 'Alt 1'},
                {'id': 11, 'src': 'https://example.com/img2.jpg', 'alt': 'Alt 2'},
              ],
            ),
          ),
          200,
        );
      });

      final repo = buildRepo(mockClient);
      final images = await repo.getProductImages('1');

      expect(images.length, 2);
      expect(images[0], isA<ProductImage>());
      expect(images[0].id, 10);
      expect(images[0].url, 'https://example.com/img1.jpg');
      expect(images[0].alt, 'Alt 1');
      expect(images[0].position, 0);
      expect(images[1].id, 11);
      expect(images[1].position, 1);
    });

    test('getProductImages returns empty list when no images', () async {
      final mockClient = MockClient((request) async {
        return http.Response(jsonEncode(_buildProductJson(id: 1, images: [])), 200);
      });

      final repo = buildRepo(mockClient);
      final images = await repo.getProductImages('1');
      expect(images, isEmpty);
    });
  });

  // ── Use cases ─────────────────────────────────────────────────────────────

  group('CreateProduct use case', () {
    test('delegates to repository and returns ProductDraft', () async {
      final repo = FakeProductPublishingRepository();
      final useCase = CreateProduct(repo);

      final result = await useCase(name: 'New Cozy', price: 12.99);

      expect(result, isA<ProductDraft>());
      expect(result.name, 'New Cozy');
      expect(result.price, 12.99);
    });
  });

  group('DeleteProduct use case', () {
    test('delegates to repository and removes product', () async {
      final repo = FakeProductPublishingRepository();
      final useCase = DeleteProduct(repo);

      final before = await repo.getProductDrafts();
      await useCase('1');
      final after = await repo.getProductDrafts();

      expect(after.length, before.length - 1);
      expect(after.any((d) => d.id == '1'), isFalse);
    });
  });

  group('GetCategories use case', () {
    test('delegates to repository and returns category list', () async {
      final repo = FakeProductPublishingRepository();
      final useCase = GetCategories(repo);

      final cats = await useCase();

      expect(cats, isA<List<ProductCategory>>());
      expect(cats.isNotEmpty, isTrue);
    });
  });

  group('CreateCategory use case', () {
    test('delegates to repository and returns new ProductCategory', () async {
      final repo = FakeProductPublishingRepository();
      final useCase = CreateCategory(repo);

      final result = await useCase(name: 'Pot Holders');

      expect(result, isA<ProductCategory>());
      expect(result.name, 'Pot Holders');
    });
  });

  group('UpdateProductImages use case', () {
    test('delegates to repository and returns updated ProductDraft', () async {
      final repo = FakeProductPublishingRepository();
      final useCase = UpdateProductImages(repo);

      final result = await useCase('1', [101, 102]);

      expect(result, isA<ProductDraft>());
      expect(result.imageUrl, contains('101'));
    });
  });
}
