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

// ── Test doubles ─────────────────────────────────────────────────────────────

class _AlwaysHealthyCheck implements IntegrationHealthCheck {
  @override
  Future<HealthCheckResult> check() async => const HealthCheckResult.healthy();
}

class _AlwaysUnhealthyCheck implements IntegrationHealthCheck {
  @override
  Future<HealthCheckResult> check() async =>
      const HealthCheckResult.unhealthy(message: 'Connection refused');
}

class _EchoWebhookHandler implements WebhookHandler {
  final String _handledSource;

  _EchoWebhookHandler(this._handledSource);

  @override
  bool canHandle(WebhookEvent event) => event.source == _handledSource;

  @override
  Future<WebhookResult> handle(WebhookEvent event) async =>
      WebhookResult.processed(message: 'handled ${event.eventType}');
}

void main() {
  // ── ApiException ────────────────────────────────────────────────────────

  group('ApiException', () {
    test('stores statusCode, message, and body', () {
      const ex = ApiException(statusCode: 404, message: 'Not found', body: '{}');
      expect(ex.statusCode, 404);
      expect(ex.message, 'Not found');
      expect(ex.body, '{}');
    });

    test('body defaults to empty string', () {
      const ex = ApiException(statusCode: 500, message: 'Server error');
      expect(ex.body, '');
    });

    test('toString includes statusCode and message', () {
      const ex = ApiException(statusCode: 401, message: 'Unauthorized');
      expect(ex.toString(), contains('401'));
      expect(ex.toString(), contains('Unauthorized'));
    });

    test('is an Exception', () {
      const ex = ApiException(statusCode: 400, message: 'Bad request');
      expect(ex, isA<Exception>());
    });
  });

  // ── RetryPolicy ─────────────────────────────────────────────────────────

  group('NoRetryPolicy', () {
    const policy = NoRetryPolicy();

    test('maxAttempts is 0', () => expect(policy.maxAttempts, 0));

    test('delayFor returns zero', () => expect(policy.delayFor(1), Duration.zero));

    test('shouldRetry always returns false', () {
      const ex = ApiException(statusCode: 503, message: 'Service unavailable');
      expect(policy.shouldRetry(ex), isFalse);
    });
  });

  group('ExponentialBackoffRetryPolicy', () {
    const policy = ExponentialBackoffRetryPolicy(
      maxAttempts: 3,
      initialDelay: Duration(milliseconds: 100),
      maxDelay: Duration(seconds: 10),
    );

    test('maxAttempts is 3', () => expect(policy.maxAttempts, 3));

    test('delay doubles with each attempt', () {
      expect(policy.delayFor(1), const Duration(milliseconds: 100));
      expect(policy.delayFor(2), const Duration(milliseconds: 200));
      expect(policy.delayFor(3), const Duration(milliseconds: 400));
    });

    test('delay is capped at maxDelay', () {
      const bigPolicy = ExponentialBackoffRetryPolicy(
        maxAttempts: 10,
        initialDelay: Duration(seconds: 5),
        maxDelay: Duration(seconds: 10),
      );
      expect(bigPolicy.delayFor(5).inSeconds, lessThanOrEqualTo(10));
    });

    test('shouldRetry returns true for 5xx errors', () {
      const ex500 = ApiException(statusCode: 500, message: 'Internal server error');
      const ex503 = ApiException(statusCode: 503, message: 'Service unavailable');
      expect(policy.shouldRetry(ex500), isTrue);
      expect(policy.shouldRetry(ex503), isTrue);
    });

    test('shouldRetry returns true for network-level errors (statusCode -1)', () {
      const ex = ApiException(statusCode: -1, message: 'Network error');
      expect(policy.shouldRetry(ex), isTrue);
    });

    test('shouldRetry returns false for 4xx client errors', () {
      const ex = ApiException(statusCode: 404, message: 'Not found');
      expect(policy.shouldRetry(ex), isFalse);
    });

    test('shouldRetry returns false for 2xx (should not happen but safe)', () {
      const ex = ApiException(statusCode: 200, message: 'OK');
      expect(policy.shouldRetry(ex), isFalse);
    });
  });

  // ── RateLimiter ─────────────────────────────────────────────────────────

  group('NoRateLimiter', () {
    test('acquire completes immediately', () async {
      const limiter = NoRateLimiter();
      await expectLater(limiter.acquire(), completes);
    });

    test('dispose does not throw', () {
      const limiter = NoRateLimiter();
      expect(() => limiter.dispose(), returnsNormally);
    });
  });

  group('TokenBucketRateLimiter', () {
    test('allows up to maxRequests without delay', () async {
      final limiter = TokenBucketRateLimiter(maxRequests: 3);
      // All three should complete without waiting.
      await limiter.acquire();
      await limiter.acquire();
      await limiter.acquire();
      limiter.dispose();
    });

    test('dispose does not throw', () {
      final limiter = TokenBucketRateLimiter(maxRequests: 5);
      expect(() => limiter.dispose(), returnsNormally);
    });
  });

  // ── HealthCheckResult ────────────────────────────────────────────────────

  group('HealthCheckResult', () {
    test('healthy constructor sets isHealthy true and default message', () {
      const result = HealthCheckResult.healthy();
      expect(result.isHealthy, isTrue);
      expect(result.message, 'OK');
      expect(result.latency, isNull);
    });

    test('healthy constructor accepts custom message and latency', () {
      const result = HealthCheckResult.healthy(
        message: 'Ping 42ms',
        latency: Duration(milliseconds: 42),
      );
      expect(result.isHealthy, isTrue);
      expect(result.message, 'Ping 42ms');
      expect(result.latency, const Duration(milliseconds: 42));
    });

    test('unhealthy constructor sets isHealthy false', () {
      const result = HealthCheckResult.unhealthy(message: 'Timeout');
      expect(result.isHealthy, isFalse);
      expect(result.message, 'Timeout');
      expect(result.latency, isNull);
    });

    test('toString includes healthy status and message', () {
      const result = HealthCheckResult.healthy(message: 'OK');
      expect(result.toString(), contains('true'));
      expect(result.toString(), contains('OK'));
    });
  });

  // ── IntegrationHealthCheck ───────────────────────────────────────────────

  group('IntegrationHealthCheck', () {
    test('healthy implementation returns healthy result', () async {
      final check = _AlwaysHealthyCheck();
      final result = await check.check();
      expect(result.isHealthy, isTrue);
    });

    test('unhealthy implementation returns unhealthy result', () async {
      final check = _AlwaysUnhealthyCheck();
      final result = await check.check();
      expect(result.isHealthy, isFalse);
      expect(result.message, 'Connection refused');
    });
  });

  // ── WebhookEvent ─────────────────────────────────────────────────────────

  group('WebhookEvent', () {
    test('stores all fields', () {
      final now = DateTime(2026, 7, 11);
      final event = WebhookEvent(
        eventType: 'order.created',
        source: 'woocommerce',
        payload: {'id': 42},
        receivedAt: now,
      );
      expect(event.eventType, 'order.created');
      expect(event.source, 'woocommerce');
      expect(event.payload['id'], 42);
      expect(event.receivedAt, now);
    });
  });

  // ── WebhookResult ────────────────────────────────────────────────────────

  group('WebhookResult', () {
    test('processed constructor sets correct status', () {
      const result = WebhookResult.processed(message: 'done');
      expect(result.status, WebhookProcessingStatus.processed);
      expect(result.message, 'done');
    });

    test('ignored constructor sets correct status', () {
      const result = WebhookResult.ignored();
      expect(result.status, WebhookProcessingStatus.ignored);
      expect(result.message, isNull);
    });

    test('failed constructor sets correct status', () {
      const result = WebhookResult.failed(message: 'error');
      expect(result.status, WebhookProcessingStatus.failed);
      expect(result.message, 'error');
    });
  });

  // ── WebhookHandler ───────────────────────────────────────────────────────

  group('WebhookHandler', () {
    final handler = _EchoWebhookHandler('woocommerce');
    final now = DateTime(2026, 7, 11);

    test('canHandle returns true for matching source', () {
      final event = WebhookEvent(
        eventType: 'product.updated',
        source: 'woocommerce',
        payload: {},
        receivedAt: now,
      );
      expect(handler.canHandle(event), isTrue);
    });

    test('canHandle returns false for non-matching source', () {
      final event = WebhookEvent(
        eventType: 'payment.completed',
        source: 'square',
        payload: {},
        receivedAt: now,
      );
      expect(handler.canHandle(event), isFalse);
    });

    test('handle returns processed result', () async {
      final event = WebhookEvent(
        eventType: 'order.created',
        source: 'woocommerce',
        payload: {},
        receivedAt: now,
      );
      final result = await handler.handle(event);
      expect(result.status, WebhookProcessingStatus.processed);
      expect(result.message, contains('order.created'));
    });
  });

  // ── ChannelAdapter value objects ─────────────────────────────────────────

  group('ChannelProduct', () {
    test('stores required and optional fields', () {
      const product = ChannelProduct(
        id: 'p1',
        name: 'Test Product',
        price: 9.99,
        status: 'publish',
        description: 'A test product',
        sku: 'SKU-001',
        imageUrl: 'https://example.com/img.jpg',
        category: 'Candles',
      );
      expect(product.id, 'p1');
      expect(product.name, 'Test Product');
      expect(product.price, 9.99);
      expect(product.status, 'publish');
      expect(product.description, 'A test product');
      expect(product.sku, 'SKU-001');
      expect(product.imageUrl, 'https://example.com/img.jpg');
      expect(product.category, 'Candles');
    });

    test('optional fields default to null', () {
      const product = ChannelProduct(id: 'p2', name: 'Minimal', price: 0, status: 'draft');
      expect(product.description, isNull);
      expect(product.sku, isNull);
      expect(product.imageUrl, isNull);
      expect(product.category, isNull);
    });
  });

  group('ChannelOrder', () {
    test('stores required and optional fields', () {
      const order = ChannelOrder(
        id: 'o1',
        status: 'processing',
        total: 29.99,
        customerName: 'Jane Doe',
        customerEmail: 'jane@example.com',
        items: [ChannelOrderItem(productId: 'p1', name: 'Candle', quantity: 2, unitPrice: 14.99)],
      );
      expect(order.id, 'o1');
      expect(order.status, 'processing');
      expect(order.total, 29.99);
      expect(order.customerName, 'Jane Doe');
      expect(order.customerEmail, 'jane@example.com');
      expect(order.items.length, 1);
      expect(order.items.first.productId, 'p1');
    });

    test('items defaults to empty list', () {
      const order = ChannelOrder(id: 'o2', status: 'pending', total: 0);
      expect(order.items, isEmpty);
    });
  });

  group('ChannelOrderItem', () {
    test('stores all fields', () {
      const item = ChannelOrderItem(
        productId: 'p1',
        name: 'Wax Melt',
        quantity: 3,
        unitPrice: 5.00,
      );
      expect(item.productId, 'p1');
      expect(item.name, 'Wax Melt');
      expect(item.quantity, 3);
      expect(item.unitPrice, 5.00);
    });
  });

  group('ChannelInventoryCount', () {
    test('stores required fields', () {
      const count = ChannelInventoryCount(productId: 'p1', quantity: 10);
      expect(count.productId, 'p1');
      expect(count.quantity, 10);
      expect(count.sku, isNull);
    });

    test('stores optional sku', () {
      const count = ChannelInventoryCount(productId: 'p1', sku: 'SKU-001', quantity: 5);
      expect(count.sku, 'SKU-001');
    });
  });
}
