import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:integrations/integrations.dart';

/// Lightweight HTTP client for the WooCommerce REST API v3.
///
/// Implements [ApiClient] so it satisfies the shared integration contract.
/// Handles authentication (Basic Auth over HTTPS) and pagination.
class WooCommerceApiClient implements ApiClient {
  /// Base URL of the WordPress site (e.g. `https://store.kellcreations.com`).
  final String siteUrl;

  /// WooCommerce REST API consumer key.
  final String consumerKey;

  /// WooCommerce REST API consumer secret.
  final String consumerSecret;

  /// Optional [http.Client] for testing / injection.
  final http.Client _httpClient;

  bool _ready = false;

  WooCommerceApiClient({
    required this.siteUrl,
    required this.consumerKey,
    required this.consumerSecret,
    http.Client? httpClient,
  }) : _httpClient = httpClient ?? http.Client();

  // ── ApiClient contract ────────────────────────────────────────────────────

  @override
  Future<void> initialize() async {
    // No async setup required for Basic Auth; mark ready immediately.
    _ready = true;
  }

  @override
  bool get isReady => _ready;

  @override
  Future<T> execute<T>(Future<T> Function() request) async {
    if (!_ready) await initialize();
    return request();
  }

  @override
  void dispose() {
    _httpClient.close();
    _ready = false;
  }

  // ── WooCommerce-specific API methods ──────────────────────────────────────

  /// The base endpoint for WooCommerce REST API v3.
  String get _baseEndpoint => '$siteUrl/wp-json/wc/v3';

  /// Fetches a paginated list of products from WooCommerce.
  ///
  /// [page] is 1-based. [perPage] defaults to 100 (WooCommerce max).
  /// Returns the raw JSON list of product maps.
  Future<List<Map<String, dynamic>>> getProducts({int page = 1, int perPage = 100}) async {
    final uri = Uri.parse(
      '$_baseEndpoint/products',
    ).replace(queryParameters: {'page': page.toString(), 'per_page': perPage.toString()});

    final response = await _httpClient.get(uri, headers: _authHeaders);

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to fetch products: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! List) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON array',
        body: response.body,
      );
    }

    return decoded.cast<Map<String, dynamic>>();
  }

  /// Fetches all products by paginating through the WooCommerce API.
  ///
  /// Keeps requesting pages until a page returns fewer items than [perPage].
  Future<List<Map<String, dynamic>>> getAllProducts({int perPage = 100}) async {
    final allProducts = <Map<String, dynamic>>[];
    var page = 1;

    while (true) {
      final batch = await getProducts(page: page, perPage: perPage);
      allProducts.addAll(batch);

      if (batch.length < perPage) break;
      page++;
    }

    return allProducts;
  }

  /// Basic Auth headers for WooCommerce REST API.
  Map<String, String> get _authHeaders {
    final credentials = base64Encode(utf8.encode('$consumerKey:$consumerSecret'));
    return {'Authorization': 'Basic $credentials', 'Content-Type': 'application/json'};
  }

  // ── Orders API ────────────────────────────────────────────────────────────

  /// Fetches a paginated list of orders from WooCommerce.
  ///
  /// [page] is 1-based. [perPage] defaults to 100 (WooCommerce max).
  /// Returns the raw JSON list of order maps.
  Future<List<Map<String, dynamic>>> getOrders({int page = 1, int perPage = 100}) async {
    final uri = Uri.parse(
      '$_baseEndpoint/orders',
    ).replace(queryParameters: {'page': page.toString(), 'per_page': perPage.toString()});

    final response = await _httpClient.get(uri, headers: _authHeaders);

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to fetch orders: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! List) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON array',
        body: response.body,
      );
    }

    return decoded.cast<Map<String, dynamic>>();
  }

  /// Fetches all orders by paginating through the WooCommerce API.
  Future<List<Map<String, dynamic>>> getAllOrders({int perPage = 100}) async {
    final allOrders = <Map<String, dynamic>>[];
    var page = 1;

    while (true) {
      final batch = await getOrders(page: page, perPage: perPage);
      allOrders.addAll(batch);
      if (batch.length < perPage) break;
      page++;
    }

    return allOrders;
  }

  /// Fetches a single order by [orderId].
  ///
  /// Returns the raw JSON order map.
  Future<Map<String, dynamic>> getOrder(String orderId) async {
    final uri = Uri.parse('$_baseEndpoint/orders/$orderId');
    final response = await _httpClient.get(uri, headers: _authHeaders);

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to fetch order $orderId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  /// Updates the status of a WooCommerce order.
  ///
  /// Returns the full updated order JSON.
  Future<Map<String, dynamic>> updateOrderStatus(String orderId, String status) async {
    final uri = Uri.parse('$_baseEndpoint/orders/$orderId');
    final response = await _httpClient.put(
      uri,
      headers: _authHeaders,
      body: jsonEncode({'status': status}),
    );

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to update order $orderId status: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  /// Creates a new order in WooCommerce.
  ///
  /// [body] should contain the WooCommerce order fields.
  /// Returns the created order JSON.
  Future<Map<String, dynamic>> createOrder(Map<String, dynamic> body) async {
    final uri = Uri.parse('$_baseEndpoint/orders');
    final response = await _httpClient.post(uri, headers: _authHeaders, body: jsonEncode(body));

    if (response.statusCode != 201) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to create order: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  /// Adds a note to a WooCommerce order.
  ///
  /// Returns the created note JSON.
  Future<Map<String, dynamic>> addOrderNote(String orderId, String note) async {
    final uri = Uri.parse('$_baseEndpoint/orders/$orderId/notes');
    final response = await _httpClient.post(
      uri,
      headers: _authHeaders,
      body: jsonEncode({'note': note}),
    );

    if (response.statusCode != 201) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to add note to order $orderId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  // ── Stock / Inventory API ─────────────────────────────────────────────────

  /// Fetches the current stock quantity for a WooCommerce product.
  ///
  /// Returns a map with at minimum `stock_quantity` (int or null) and
  /// `stock_status` (string: `'instock'`, `'outofstock'`, `'onbackorder'`).
  Future<Map<String, dynamic>> getProductStock(String productId) async {
    final uri = Uri.parse('$_baseEndpoint/products/$productId');
    final response = await _httpClient.get(uri, headers: _authHeaders);

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to fetch stock for product $productId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return {
      'id': decoded['id'],
      'sku': decoded['sku'],
      'stock_quantity': decoded['stock_quantity'],
      'stock_status': decoded['stock_status'],
      'manage_stock': decoded['manage_stock'],
    };
  }

  /// Updates the stock quantity for a WooCommerce product.
  ///
  /// Sets `manage_stock` to `true` and `stock_quantity` to [quantity].
  /// Returns the updated product JSON (stock fields only).
  Future<Map<String, dynamic>> updateProductStock(String productId, int quantity) async {
    final uri = Uri.parse('$_baseEndpoint/products/$productId');
    final response = await _httpClient.put(
      uri,
      headers: _authHeaders,
      body: jsonEncode({'manage_stock': true, 'stock_quantity': quantity}),
    );

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to update stock for product $productId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return {
      'id': decoded['id'],
      'sku': decoded['sku'],
      'stock_quantity': decoded['stock_quantity'],
      'stock_status': decoded['stock_status'],
      'manage_stock': decoded['manage_stock'],
    };
  }

  /// Batch-updates stock quantities for multiple WooCommerce products.
  ///
  /// [updates] is a list of maps, each with `id` (int) and `stock_quantity` (int).
  /// Uses the WooCommerce batch update endpoint (`POST /products/batch`).
  /// Returns the list of updated product stock maps.
  Future<List<Map<String, dynamic>>> batchUpdateStock(List<Map<String, dynamic>> updates) async {
    final uri = Uri.parse('$_baseEndpoint/products/batch');
    final body = {
      'update': updates
          .map((u) => {'id': u['id'], 'manage_stock': true, 'stock_quantity': u['stock_quantity']})
          .toList(),
    };

    final response = await _httpClient.post(uri, headers: _authHeaders, body: jsonEncode(body));

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to batch update stock: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    final updated = decoded['update'];
    if (updated is! List) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected batch response: missing "update" array',
        body: response.body,
      );
    }

    return updated
        .cast<Map<String, dynamic>>()
        .map(
          (p) => {
            'id': p['id'],
            'sku': p['sku'],
            'stock_quantity': p['stock_quantity'],
            'stock_status': p['stock_status'],
            'manage_stock': p['manage_stock'],
          },
        )
        .toList();
  }

  // ── Products API ──────────────────────────────────────────────────────────

  /// Sends a partial update (PUT) to a single WooCommerce product.
  ///
  /// Only the fields present in [body] are changed on the remote product.
  /// Returns the full product JSON that WooCommerce echoes back.
  Future<Map<String, dynamic>> updateProduct(String productId, Map<String, dynamic> body) async {
    final uri = Uri.parse('$_baseEndpoint/products/$productId');

    final response = await _httpClient.put(uri, headers: _authHeaders, body: jsonEncode(body));

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to update product $productId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  /// Creates a new product in WooCommerce.
  ///
  /// [body] should contain the WooCommerce product fields (name, regular_price,
  /// description, sku, status, categories, etc.).
  /// Returns the created product JSON (HTTP 201).
  Future<Map<String, dynamic>> createProduct(Map<String, dynamic> body) async {
    final uri = Uri.parse('$_baseEndpoint/products');
    final response = await _httpClient.post(uri, headers: _authHeaders, body: jsonEncode(body));

    if (response.statusCode != 201) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to create product: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  /// Deletes a WooCommerce product by [productId].
  ///
  /// When [force] is `true` the product is permanently deleted; otherwise it
  /// is moved to trash (WooCommerce default).
  /// Returns the deleted product JSON that WooCommerce echoes back.
  Future<Map<String, dynamic>> deleteProduct(String productId, {bool force = false}) async {
    final uri = Uri.parse(
      '$_baseEndpoint/products/$productId',
    ).replace(queryParameters: {'force': force.toString()});

    final response = await _httpClient.delete(uri, headers: _authHeaders);

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to delete product $productId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  // ── Categories API ────────────────────────────────────────────────────────

  /// Fetches all product categories from WooCommerce.
  ///
  /// Paginates automatically until all categories are retrieved.
  /// Returns the raw JSON list of category maps.
  Future<List<Map<String, dynamic>>> getCategories({int perPage = 100}) async {
    final allCategories = <Map<String, dynamic>>[];
    var page = 1;

    while (true) {
      final uri = Uri.parse(
        '$_baseEndpoint/products/categories',
      ).replace(queryParameters: {'page': page.toString(), 'per_page': perPage.toString()});

      final response = await _httpClient.get(uri, headers: _authHeaders);

      if (response.statusCode != 200) {
        throw WooCommerceApiException(
          statusCode: response.statusCode,
          message: 'Failed to fetch categories: ${response.reasonPhrase}',
          body: response.body,
        );
      }

      final decoded = jsonDecode(response.body);
      if (decoded is! List) {
        throw WooCommerceApiException(
          statusCode: response.statusCode,
          message: 'Unexpected response format: expected a JSON array',
          body: response.body,
        );
      }

      final batch = decoded.cast<Map<String, dynamic>>();
      allCategories.addAll(batch);
      if (batch.length < perPage) break;
      page++;
    }

    return allCategories;
  }

  /// Creates a new product category in WooCommerce.
  ///
  /// [body] should contain at minimum `name`. Optional: `slug`, `parent`.
  /// Returns the created category JSON (HTTP 201).
  Future<Map<String, dynamic>> createCategory(Map<String, dynamic> body) async {
    final uri = Uri.parse('$_baseEndpoint/products/categories');
    final response = await _httpClient.post(uri, headers: _authHeaders, body: jsonEncode(body));

    if (response.statusCode != 201) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to create category: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  /// Updates an existing product category in WooCommerce.
  ///
  /// [body] may contain `name`, `slug`, `parent`, `description`, etc.
  /// Returns the updated category JSON.
  Future<Map<String, dynamic>> updateCategory(int categoryId, Map<String, dynamic> body) async {
    final uri = Uri.parse('$_baseEndpoint/products/categories/$categoryId');
    final response = await _httpClient.put(uri, headers: _authHeaders, body: jsonEncode(body));

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to update category $categoryId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  /// Deletes a product category by [categoryId].
  ///
  /// WooCommerce requires [force] = `true` to permanently delete a category.
  /// Returns the deleted category JSON.
  Future<Map<String, dynamic>> deleteCategory(int categoryId, {bool force = true}) async {
    final uri = Uri.parse(
      '$_baseEndpoint/products/categories/$categoryId',
    ).replace(queryParameters: {'force': force.toString()});

    final response = await _httpClient.delete(uri, headers: _authHeaders);

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to delete category $categoryId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }

  // ── Media / Images API ────────────────────────────────────────────────────

  /// Fetches the images attached to a WooCommerce product.
  ///
  /// Returns the raw `images` array from the product JSON.
  Future<List<Map<String, dynamic>>> getProductImages(String productId) async {
    final uri = Uri.parse('$_baseEndpoint/products/$productId');
    final response = await _httpClient.get(uri, headers: _authHeaders);

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to fetch product $productId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    final images = decoded['images'];
    if (images is! List) return [];
    return images.cast<Map<String, dynamic>>();
  }

  /// Sets the images on a WooCommerce product by media attachment IDs.
  ///
  /// [imageIds] is an ordered list of WooCommerce media attachment IDs.
  /// The first ID becomes the featured image (position 0).
  /// Pass an empty list to remove all images.
  ///
  /// Returns the full updated product JSON.
  Future<Map<String, dynamic>> setProductImages(String productId, List<int> imageIds) async {
    final images = imageIds.asMap().entries.map((e) => {'id': e.value}).toList();

    final uri = Uri.parse('$_baseEndpoint/products/$productId');
    final response = await _httpClient.put(
      uri,
      headers: _authHeaders,
      body: jsonEncode({'images': images}),
    );

    if (response.statusCode != 200) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Failed to set images for product $productId: ${response.reasonPhrase}',
        body: response.body,
      );
    }

    final decoded = jsonDecode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw WooCommerceApiException(
        statusCode: response.statusCode,
        message: 'Unexpected response format: expected a JSON object',
        body: response.body,
      );
    }

    return decoded;
  }
}

/// Exception thrown when the WooCommerce API returns an error.
///
/// Extends the shared [ApiException] contract so callers can catch either
/// [WooCommerceApiException] for WooCommerce-specific handling or
/// [ApiException] for generic error handling.
class WooCommerceApiException extends ApiException {
  const WooCommerceApiException({
    required super.statusCode,
    required super.message,
    super.body = '',
  });

  @override
  String toString() => 'WooCommerceApiException($statusCode): $message';
}
