import 'package:guru_app/database/guru_db.dart';
import 'package:guru_app/financial/product/product_model.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/id/id_utils.dart';
import 'package:guru_utils/log/log.dart';
import 'package:sqflite/sqflite.dart';
import 'package:json_annotation/json_annotation.dart';

import '../../manifest/manifest.dart';

/// Created by Haoyi on 2022/9/5

part 'order_database.g.dart';

@JsonSerializable()
class AssetEntity {
  static const tbName = "assets"; // Product Transaction Table
  static const dbSku = "sku";
  static const dbState = "state";
  static const dbAttribute = "attr"; // 商品属性（所有，消耗品，订阅）
  static const dbMethod = "method"; // 购买方式(免费获取，金币购买，IAP购买，激励视频购买)
  static const dbCurrency = "currency"; // 货币类型(法币，虚拟币)
  static const dbCost = "cost"; // 消费多少
  static const dbTimestamp = "ts";
  static const dbManifest = "manifest"; // 本次交易中的清单
}

@JsonSerializable()
class OrderEntity {
  static const tbName = "orders"; // Product Transaction Table
  static const dbOrderId = "oid";
  static const dbSku = "sku";
  static const dbState = "state";
  static const dbErrorInfo = "err_info";
  static const dbAttribute = "attr"; // 商品属性（所有，消耗品，订阅）
  static const dbMethod = "method"; // 购买方式(免费获取，金币购买，IAP购买，激励视频购买)
  static const dbCurrency = "currency"; // 货币类型(法币，虚拟币)
  static const dbCategory = "category";
  static const dbCost = "cost"; // 消费多少
  static const dbTimestamp = "ts";
  static const dbManifest = "manifest"; // 本次交易中的清单

  @JsonKey(name: dbOrderId)
  final String orderId;

  @JsonKey(name: dbSku)
  final String sku;

  @JsonKey(name: dbState)
  final int state;

  @JsonKey(name: dbAttribute)
  final int attr;

  @JsonKey(name: dbMethod)
  final int method;

  @JsonKey(name: dbErrorInfo, defaultValue: "")
  final String errorInfo;

  @JsonKey(name: dbCurrency, defaultValue: "")
  final String currency;

  @JsonKey(name: dbCost, defaultValue: 0.0)
  final double cost;

  @JsonKey(name: dbTimestamp, defaultValue: 0)
  final int timestamp;

  @JsonKey(name: dbCategory, defaultValue: "")
  final String category;

  @JsonKey(name: dbManifest)
  @manifestStringConvert
  final Manifest? manifest;

  @JsonKey(ignore: true)
  ProductId? _productId;

  OrderEntity(
      {required this.orderId,
      required this.sku,
      required this.state,
      required this.attr,
      required this.method,
      required this.currency,
      required this.cost,
      required this.timestamp,
      required this.category,
      required this.manifest,
      this.errorInfo = ""});

  OrderEntity success() {
    return OrderEntity(
        orderId: orderId,
        sku: sku,
        state: TransactionState.success,
        attr: attr,
        method: method,
        currency: currency,
        cost: cost,
        category: category,
        timestamp: DateTimeUtils.currentTimeInMillis(),
        manifest: manifest);
  }

  OrderEntity error(String errorInfo) {
    return OrderEntity(
        orderId: orderId,
        sku: sku,
        state: TransactionState.error,
        attr: attr,
        method: method,
        currency: currency,
        cost: cost,
        category: category,
        timestamp: DateTimeUtils.currentTimeInMillis(),
        manifest: manifest,
        errorInfo: errorInfo);
  }

  OrderEntity expired() {
    return OrderEntity(
        orderId: orderId,
        sku: sku,
        state: TransactionState.expired,
        attr: attr,
        method: method,
        currency: currency,
        cost: cost,
        category: category,
        timestamp: DateTimeUtils.currentTimeInMillis(),
        manifest: manifest,
        errorInfo: "Subscription Expired");
  }

  bool get isConsumable => attr == TransactionAttributes.consumable;

  bool get isAsset => attr == TransactionAttributes.asset;

  bool get isSubscription => attr == TransactionAttributes.subscriptions;

  bool get isSuccess => state == TransactionState.success;

  static Future createTable(Transaction delegate) async {
    // v1
    const v1Fields = "$dbOrderId TEXT PRIMARY KEY,"
        "$dbSku TEXT NOT NULL,"
        "$dbState INTEGER NOT NULL,"
        "$dbAttribute INTEGER NOT NULL,"
        "$dbMethod INTEGER NOT NULL,"
        "$dbErrorInfo TEXT DEFAULT '',"
        "$dbCurrency TEXT NOT NULL,"
        "$dbCost REAL DEFAULT 0.0,"
        "$dbTimestamp INTEGER NOT NULL,"
        "$dbManifest TEXT DEFAULT '',";

    const v2Fields = "$dbCategory TEXT DEFAULT ''";

    const cmd = "CREATE TABLE $tbName ("
        "$v1Fields"
        "$v2Fields"
        ");";

    Log.v("#### cmd: $cmd");

    await delegate.execute(cmd);
    await delegate.execute("CREATE INDEX trans_sku_idx ON $tbName ($dbSku);");
    await delegate.execute("CREATE INDEX trans_category_idx ON $tbName ($dbCategory);");
  }

  @override
  String toString() {
    return 'OrderEntity{tid: $dbOrderId, sku: $sku, state: $state, attr: $attr, method: $method, errorInfo: $errorInfo, currency: $currency, cost: $cost, timestamp: $timestamp, manifest: $manifest}';
  }

  factory OrderEntity.fromMap(Map<String, dynamic> json) => _$OrderEntityFromJson(json);

  Map<String, dynamic> toMap() => _$OrderEntityToJson(this);

  Map<String, dynamic> toUpdateMap() => toMap()..remove(dbOrderId);

  ProductId get productId {
    _productId ??= GuruApp.instance.defineProductId(sku, attr, TransactionMethod.values[method]);
    return _productId!;
  }
}

extension OrderDatabase on GuruDB {
  Future<Map<String, String>> loadAllOrders() async {
    final db = getDb();
    final result = await db.rawQuery("SELECT * FROM ${OrderEntity.tbName}");
    if (result.isNotEmpty) {
      return {for (var map in result) map[OrderEntity.dbOrderId] as String: map.toString()};
    }
    return <String, String>{};
  }

  Future<List<OrderEntity>> selectOrders(
      {required TransactionMethod method,
      required List<int> attrs,
      int state = TransactionState.success}) async {
    final db = getDb();
    final List<String> conditions = [
      "${OrderEntity.dbMethod} = ${method.index}",
      "${OrderEntity.dbState} = $state"
    ];
    if (attrs.isNotEmpty) {
      conditions.add("${OrderEntity.dbAttribute} IN (${attrs.map((attr) => '"$attr"').join(",")})");
    }
    final result =
        await db.rawQuery("SELECT * FROM ${OrderEntity.tbName} WHERE ${conditions.join(" AND ")}");
    if (result.isNotEmpty) {
      return result.map((map) => OrderEntity.fromMap(map)).toList();
    }
    return [];
  }

  Future<List<OrderEntity>> getCompleteOrders(ProductId productId) async {
    final db = getDb();
    String where =
        "${OrderEntity.dbSku} = '${productId.sku}' AND ${OrderEntity.dbState} = ${TransactionState.success}";
    final result = await db.rawQuery("SELECT * FROM ${OrderEntity.tbName} WHERE $where");
    if (result.isNotEmpty) {
      return result.map((map) => OrderEntity.fromMap(map)).toList();
    }
    return [];
  }

  Future<List<OrderEntity>> getPendingOrders(ProductId productId,
      {TransactionMethod method = TransactionMethod.iap}) async {
    final db = getDb();
    String where =
        "${OrderEntity.dbSku} = '${productId.sku}' AND ${OrderEntity.dbMethod} = ${method.index} AND ${OrderEntity.dbState} = ${TransactionState.pending}";
    final result = await db.rawQuery(
        "SELECT * FROM ${OrderEntity.tbName} WHERE $where ORDER BY ${OrderEntity.dbTimestamp} DESC");
    if (result.isNotEmpty) {
      return result.map((map) => OrderEntity.fromMap(map)).toList();
    }
    return [];
  }

  Future<Map<String, Object>> completePendingOrders(Set<ProductId> productIds,
      {TransactionMethod method = TransactionMethod.iap}) async {
    final db = getDb();
    final batch = db.batch();
    final updateValues = <String, Object>{
      OrderEntity.dbState: TransactionState.success,
      OrderEntity.dbTimestamp: DateTimeUtils.currentTimeInMillis()
    };
    for (var productId in productIds) {
      final String where =
          "${OrderEntity.dbSku} = '${productId.sku}' AND ${OrderEntity.dbMethod} = ${method.index} AND ${OrderEntity.dbState} = ${TransactionState.pending}";
      if (productId.isConsumable) {
        batch.delete(
          OrderEntity.tbName,
          where: where,
        );
      } else {
        batch.update(
          OrderEntity.tbName,
          updateValues,
          where: where,
        );
      }
    }
    await batch.commit();
    return updateValues;
  }

  Future<bool> removePendingOrders(Set<ProductId> productIds,
      {TransactionMethod method = TransactionMethod.iap}) async {
    final db = getDb();
    final batch = db.batch();
    for (var productId in productIds) {
      final String where =
          "${OrderEntity.dbSku} = '${productId.sku}' AND ${OrderEntity.dbMethod} = ${method.index} AND ${OrderEntity.dbState} = ${TransactionState.pending}";
      batch.delete(
        OrderEntity.tbName,
        where: where,
      );
    }
    batch.commit();
    return true;
  }

  Future<bool> upsertOrder({required OrderEntity order}) {
    return runInTransaction((txn) async {
      final upsertMap = order.toMap();
      if (!order.isConsumable) {
        final result = await txn.rawQuery(
            "SELECT * FROM ${OrderEntity.tbName} WHERE ${OrderEntity.dbSku} = '${order.sku}'");
        if (result.isNotEmpty) {
          upsertMap[OrderEntity.dbOrderId] = result.first[OrderEntity.dbOrderId] ?? order.orderId;
        }
      }
      await txn.insert(OrderEntity.tbName, upsertMap, conflictAlgorithm: ConflictAlgorithm.replace);
      return true;
    });
  }

  Future<bool> replaceOrderBySku({required OrderEntity order}) {
    return runInTransaction((txn) async {
      final replaceMap = order.toMap();
      if (!order.isConsumable) {
        await txn.rawDelete(
            "DELETE FROM ${OrderEntity.tbName} WHERE ${OrderEntity.dbSku} = '${order.sku}'");
      }
      await txn.insert(OrderEntity.tbName, replaceMap,
          conflictAlgorithm: ConflictAlgorithm.replace);
      return true;
    });
  }

  Future<bool> completeOrder({required OrderEntity order}) async {
    final db = getDb();
    if (order.isConsumable) {
      await db.rawDelete(
          "DELETE FROM ${OrderEntity.tbName} WHERE ${OrderEntity.dbOrderId} = '${order.orderId}'");
    } else {
      await db.insert(OrderEntity.tbName, order.toMap(),
          conflictAlgorithm: ConflictAlgorithm.replace);
    }
    return true;
  }

  Future<bool> upsertOrders(List<OrderEntity> orders) {
    final possessiveOrders = orders.where((order) => !order.isConsumable).toList();
    final skus = possessiveOrders.map((order) => "'${order.sku}'").toList();

    return runInTransaction((txn) async {
      await txn.rawDelete(
          "DELETE FROM ${OrderEntity.tbName} WHERE ${OrderEntity.dbSku} IN (${skus.join(",")})");
      for (var upsertOrder in possessiveOrders) {
        final upsertOrderJson = upsertOrder.toMap();
        await txn.insert(OrderEntity.tbName, upsertOrderJson,
            conflictAlgorithm: ConflictAlgorithm.replace);
      }
      return true;
    });
  }

  Future<bool> deleteOrder({required OrderEntity order}) async {
    final result = await getDb().rawDelete(
        "DELETE FROM ${OrderEntity.tbName} WHERE ${OrderEntity.dbOrderId} = '${order.orderId}'");
    return result > 0;
  }

  Future<bool> deleteOrdersBySkus(Set<String> skus) async {
    final result = await getDb().rawDelete(
        "DELETE FROM ${OrderEntity.tbName} WHERE ${OrderEntity.dbSku} IN (${skus.map((sku) => "'$sku'").join(",")})");
    return result > 0;
  }

  Future<bool> deletePendingOrderBySku({required String sku}) async {
    return runInTransaction((txn) async {
      String where =
          "${OrderEntity.dbSku} = '$sku' AND ${OrderEntity.dbState} = ${TransactionState.pending}";
      final result = await txn.rawQuery(
          "SELECT * FROM ${OrderEntity.tbName} WHERE $where ORDER BY ${OrderEntity.dbTimestamp} DESC");
      if (result.isNotEmpty) {
        final order = OrderEntity.fromMap(result[0]);
        await txn.rawDelete(
            "DELETE FROM ${OrderEntity.tbName} WHERE ${OrderEntity.dbOrderId} = '${order.orderId}'");
        return true;
      }
      return false;
    });
  }

  Future clearTransaction() async {
    final db = getDb();
    db.delete(OrderEntity.tbName);
  }

  Future<bool> clearOrders({String? category, TransactionMethod? method}) async {
    final db = getDb();
    final whereList = [];
    if (category != null) {
      whereList.add("${OrderEntity.dbCategory} = '$category'");
    }
    if (method != null) {
      whereList.add("${OrderEntity.dbMethod} = ${method.index}");
    }
    final whereCondition = whereList.join(" AND ");
    Log.d("clearOrders: $whereCondition");
    db.rawDelete("DELETE FROM ${OrderEntity.tbName} WHERE $whereCondition");
    return true;
  }
}
