import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/network/api_client.dart';
import 'package:superport_v2/features/inventory/transactions/data/repositories/stock_transaction_repository_remote.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction_input.dart';
import 'package:superport_v2/features/inventory/transactions/domain/repositories/stock_transaction_repository.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  const baseUrl = String.fromEnvironment('STAGING_API_BASE_URL');
  const token = String.fromEnvironment('STAGING_API_TOKEN');
  const runFlow = bool.fromEnvironment('STAGING_RUN_TRANSACTION_FLOW');
  const useFakeFlow = bool.fromEnvironment('STAGING_USE_FAKE_FLOW');

  const transactionTypeId = int.fromEnvironment(
    'STAGING_TRANSACTION_TYPE_ID',
    defaultValue: 0,
  );
  const transactionStatusId = int.fromEnvironment(
    'STAGING_TRANSACTION_STATUS_ID',
    defaultValue: 0,
  );
  const warehouseId = int.fromEnvironment(
    'STAGING_WAREHOUSE_ID',
    defaultValue: 0,
  );
  const employeeId = int.fromEnvironment(
    'STAGING_EMPLOYEE_ID',
    defaultValue: 0,
  );
  const productId = int.fromEnvironment('STAGING_PRODUCT_ID', defaultValue: 0);
  const customerId = int.fromEnvironment(
    'STAGING_CUSTOMER_ID',
    defaultValue: 0,
  );

  final missingConfigs = <String>[];
  if (!runFlow) {
    missingConfigs.add('STAGING_RUN_TRANSACTION_FLOW=true');
  }

  if (!useFakeFlow) {
    if (baseUrl.isEmpty) {
      missingConfigs.add('STAGING_API_BASE_URL');
    }
    if (token.isEmpty) {
      missingConfigs.add('STAGING_API_TOKEN');
    }
    if (transactionTypeId == 0) {
      missingConfigs.add('STAGING_TRANSACTION_TYPE_ID');
    }
    if (transactionStatusId == 0) {
      missingConfigs.add('STAGING_TRANSACTION_STATUS_ID');
    }
    if (warehouseId == 0) {
      missingConfigs.add('STAGING_WAREHOUSE_ID');
    }
    if (employeeId == 0) {
      missingConfigs.add('STAGING_EMPLOYEE_ID');
    }
    if (productId == 0) {
      missingConfigs.add('STAGING_PRODUCT_ID');
    }
    if (customerId == 0) {
      missingConfigs.add('STAGING_CUSTOMER_ID');
    }
  }

  if (missingConfigs.isNotEmpty) {
    final reason = '환경 변수를 설정하세요: ${missingConfigs.join(', ')}';
    testWidgets(
      'staging transaction flow (환경 변수 설정 필요: STAGING_RUN_TRANSACTION_FLOW 및 API 식별자)',
      (tester) async {
        tester.printToConsole('통합 테스트를 실행하려면 다음 값을 설정하세요.\n$reason');
      },
      skip: true,
    );
    return;
  }

  testWidgets('stock transaction end-to-end flow succeeds', (tester) async {
    final resolvedTransactionTypeId = transactionTypeId == 0
        ? 100
        : transactionTypeId;
    final resolvedTransactionStatusId = transactionStatusId == 0
        ? 10
        : transactionStatusId;
    final resolvedWarehouseId = warehouseId == 0 ? 1 : warehouseId;
    final resolvedEmployeeId = employeeId == 0 ? 1 : employeeId;
    final resolvedProductId = productId == 0 ? 1 : productId;
    final resolvedCustomerId = customerId == 0 ? 1 : customerId;

    late final StockTransactionRepository repository;
    if (useFakeFlow) {
      repository = _FakeStockTransactionRepository(
        transactionTypeId: resolvedTransactionTypeId,
        initialStatusId: resolvedTransactionStatusId,
        warehouseId: resolvedWarehouseId,
        employeeId: resolvedEmployeeId,
        productId: resolvedProductId,
        customerId: resolvedCustomerId,
      );
    } else {
      final dio = Dio(
        BaseOptions(
          baseUrl: baseUrl,
          headers: {
            'Authorization': 'Bearer $token',
            'Accept': 'application/json',
          },
        ),
      );
      final apiClient = ApiClient(dio: dio);
      repository = StockTransactionRepositoryRemote(apiClient: apiClient);
    }

    final now = DateTime.now();

    StockTransactionCreateInput buildCreateInput(String label) {
      final suffix = '$label-${now.millisecondsSinceEpoch}';
      return StockTransactionCreateInput(
        transactionTypeId: resolvedTransactionTypeId,
        transactionStatusId: resolvedTransactionStatusId,
        warehouseId: resolvedWarehouseId,
        transactionDate: now,
        createdById: resolvedEmployeeId,
        note: 'integration-test $suffix',
        lines: [
          TransactionLineCreateInput(
            lineNo: 1,
            productId: resolvedProductId,
            quantity: 1,
            unitPrice: 1,
            note: 'line $suffix',
          ),
        ],
        customers: [
          TransactionCustomerCreateInput(customerId: resolvedCustomerId),
        ],
        approval: StockTransactionApprovalInput(
          requestedById: resolvedEmployeeId,
          note: 'approval $suffix',
        ),
      );
    }

    bool statusChanged(StockTransaction before, StockTransaction after) {
      if (before.status.id != after.status.id) {
        return true;
      }
      final beforeName = before.status.name.trim();
      final afterName = after.status.name.trim();
      return beforeName != afterName;
    }

    Future<void> safeDelete(int id) async {
      try {
        await repository.delete(id);
        tester.printToConsole('deleted transaction: $id');
      } catch (error) {
        tester.printToConsole(
          '삭제 중 경고: transaction $id 제거 실패 (${error.runtimeType})',
        );
      }
    }

    final primary = await repository.create(buildCreateInput('primary'));
    expect(primary.id, isNotNull);
    final primaryId = primary.id!;
    tester.printToConsole('created transaction(primary): $primaryId');

    final submitted = await repository.submit(
      primaryId,
      note: 'integration-submit-primary',
    );
    expect(submitted.id, equals(primaryId));
    if (useFakeFlow) {
      expect(submitted.status.name, contains('승인대기'));
    } else {
      expect(statusChanged(primary, submitted), isTrue);
    }
    tester.printToConsole(
      'submitted transaction: $primaryId (status: ${submitted.status.name})',
    );

    final approved = await repository.approve(
      primaryId,
      note: 'integration-approve-primary',
    );
    expect(approved.id, equals(primaryId));
    if (useFakeFlow) {
      expect(approved.status.name, contains('승인완료'));
    } else {
      expect(statusChanged(submitted, approved), isTrue);
    }
    tester.printToConsole(
      'approved transaction: $primaryId (status: ${approved.status.name})',
    );

    final completed = await repository.complete(
      primaryId,
      note: 'integration-complete-primary',
    );
    expect(completed.id, equals(primaryId));
    if (useFakeFlow) {
      expect(completed.status.name, contains('완료'));
    } else {
      expect(statusChanged(approved, completed), isTrue);
    }
    tester.printToConsole(
      'completed transaction: $primaryId (status: ${completed.status.name})',
    );
    await safeDelete(primaryId);

    final cancelTarget = await repository.create(buildCreateInput('cancel'));
    expect(cancelTarget.id, isNotNull);
    final cancelId = cancelTarget.id!;
    tester.printToConsole('created transaction(cancel): $cancelId');

    final cancelSubmitted = await repository.submit(
      cancelId,
      note: 'integration-submit-cancel',
    );
    expect(cancelSubmitted.id, equals(cancelId));
    final cancelled = await repository.cancel(
      cancelId,
      note: 'integration-cancel',
    );
    expect(cancelled.id, equals(cancelId));
    if (useFakeFlow) {
      expect(cancelled.status.name, contains('취소'));
    } else {
      expect(statusChanged(cancelSubmitted, cancelled), isTrue);
    }
    tester.printToConsole(
      'cancelled transaction: $cancelId (status: ${cancelled.status.name})',
    );
    await safeDelete(cancelId);

    final rejectTarget = await repository.create(buildCreateInput('reject'));
    expect(rejectTarget.id, isNotNull);
    final rejectId = rejectTarget.id!;
    tester.printToConsole('created transaction(reject): $rejectId');

    final rejectSubmitted = await repository.submit(
      rejectId,
      note: 'integration-submit-reject',
    );
    expect(rejectSubmitted.id, equals(rejectId));
    final rejected = await repository.reject(
      rejectId,
      note: 'integration-reject',
    );
    expect(rejected.id, equals(rejectId));
    if (useFakeFlow) {
      expect(rejected.status.name, contains('반려'));
    } else {
      expect(statusChanged(rejectSubmitted, rejected), isTrue);
    }
    tester.printToConsole(
      'rejected transaction: $rejectId (status: ${rejected.status.name})',
    );
    await safeDelete(rejectId);
  });
}

class _FakeStockTransactionRepository implements StockTransactionRepository {
  _FakeStockTransactionRepository({
    required this.transactionTypeId,
    required this.initialStatusId,
    required this.warehouseId,
    required this.employeeId,
    required this.productId,
    required this.customerId,
  });

  final int transactionTypeId;
  final int initialStatusId;
  final int warehouseId;
  final int employeeId;
  final int productId;
  final int customerId;

  int _sequence = 1;
  final Map<int, StockTransaction> _transactions = {};

  @override
  Future<StockTransaction> create(StockTransactionCreateInput input) async {
    final id = _sequence++;
    final generatedNo = 'FAKE-${id.toString().padLeft(6, '0')}';
    final transaction = StockTransaction(
      id: id,
      transactionNo: generatedNo,
      transactionDate: input.transactionDate,
      type: StockTransactionType(id: input.transactionTypeId, name: '테스트 트랜잭션'),
      status: StockTransactionStatus(id: initialStatusId, name: '작성중'),
      warehouse: StockTransactionWarehouse(
        id: warehouseId,
        code: 'WH-$warehouseId',
        name: '테스트 창고',
      ),
      createdBy: StockTransactionEmployee(
        id: employeeId,
        employeeNo: 'EMP-$employeeId',
        name: '통합 테스트 사용자',
      ),
      note: input.note,
      isActive: true,
      lines: input.lines
          .map(
            (line) => StockTransactionLine(
              id: line.lineNo,
              lineNo: line.lineNo,
              product: StockTransactionProduct(
                id: line.productId,
                code: 'P-${line.productId}',
                name: '테스트 상품',
              ),
              quantity: line.quantity,
              unitPrice: line.unitPrice,
              note: line.note,
            ),
          )
          .toList(growable: false),
      customers: input.customers
          .map(
            (customer) => StockTransactionCustomer(
              id: customer.customerId,
              customer: StockTransactionCustomerSummary(
                id: customer.customerId,
                code: 'C-${customer.customerId}',
                name: '테스트 고객',
              ),
              note: customer.note,
            ),
          )
          .toList(growable: false),
      expectedReturnDate: input.expectedReturnDate,
    );
    _transactions[id] = transaction;
    return transaction;
  }

  @override
  Future<StockTransaction> submit(int id, {String? note}) async {
    return _updateStatus(
      id,
      StockTransactionStatus(id: initialStatusId + 1, name: '승인대기'),
    );
  }

  @override
  Future<StockTransaction> complete(int id, {String? note}) async {
    return _updateStatus(
      id,
      StockTransactionStatus(id: initialStatusId + 2, name: '완료'),
    );
  }

  @override
  Future<StockTransaction> approve(int id, {String? note}) async {
    return _updateStatus(
      id,
      StockTransactionStatus(id: initialStatusId + 3, name: '승인완료'),
    );
  }

  @override
  Future<StockTransaction> reject(int id, {String? note}) async {
    return _updateStatus(
      id,
      StockTransactionStatus(id: initialStatusId + 4, name: '반려'),
    );
  }

  @override
  Future<StockTransaction> cancel(int id, {String? note}) async {
    return _updateStatus(
      id,
      StockTransactionStatus(id: initialStatusId + 5, name: '취소'),
    );
  }

  @override
  Future<void> delete(int id) async {
    _transactions.remove(id);
  }

  @override
  Future<StockTransaction> restore(int id) async {
    return _require(id);
  }

  @override
  Future<StockTransaction> update(
    int id,
    StockTransactionUpdateInput input,
  ) async {
    final current = _require(id);
    final updated = current.copyWith(
      status: StockTransactionStatus(
        id: input.transactionStatusId,
        name: '상태${input.transactionStatusId}',
      ),
      note: input.note ?? current.note,
      expectedReturnDate:
          input.expectedReturnDate ?? current.expectedReturnDate,
    );
    _transactions[id] = updated;
    return updated;
  }

  @override
  Future<StockTransaction> fetchDetail(
    int id, {
    List<String> include = const ['lines', 'customers', 'approval'],
  }) async {
    return _require(id);
  }

  @override
  Future<PaginatedResult<StockTransaction>> list({
    StockTransactionListFilter? filter,
  }) async {
    final items = _transactions.values.toList(growable: false);
    return PaginatedResult<StockTransaction>(
      items: items,
      page: filter?.page ?? 1,
      pageSize: filter?.pageSize ?? items.length,
      total: items.length,
    );
  }

  StockTransaction _updateStatus(int id, StockTransactionStatus status) {
    final current = _require(id);
    final updated = current.copyWith(status: status);
    _transactions[id] = updated;
    return updated;
  }

  StockTransaction _require(int id) {
    final transaction = _transactions[id];
    if (transaction == null) {
      throw StateError('Transaction $id not found');
    }
    return transaction;
  }
}
