import 'package:flutter_test/flutter_test.dart';

/// 테스트 가능한 액션의 기본 인터페이스
abstract class TestableAction {
  /// 액션 이름
  String get name;
  
  /// 액션 설명
  String get description;
  
  /// 액션 실행 전 조건 검증
  Future<bool> canExecute(WidgetTester tester);
  
  /// 액션 실행
  Future<ActionResult> execute(WidgetTester tester);
  
  /// 액션 실행 후 검증
  Future<bool> verify(WidgetTester tester);
  
  /// 에러 발생 시 복구 시도
  Future<bool> recover(WidgetTester tester, dynamic error);
}

/// 액션 실행 결과
class ActionResult {
  final bool success;
  final String? message;
  final dynamic data;
  final Duration executionTime;
  final Map<String, dynamic>? metrics;
  final dynamic error;
  final StackTrace? stackTrace;

  ActionResult({
    required this.success,
    this.message,
    this.data,
    required this.executionTime,
    this.metrics,
    this.error,
    this.stackTrace,
  });

  factory ActionResult.success({
    String? message,
    dynamic data,
    required Duration executionTime,
    Map<String, dynamic>? metrics,
  }) {
    return ActionResult(
      success: true,
      message: message,
      data: data,
      executionTime: executionTime,
      metrics: metrics,
    );
  }

  factory ActionResult.failure({
    required String message,
    required Duration executionTime,
    dynamic error,
    StackTrace? stackTrace,
  }) {
    return ActionResult(
      success: false,
      message: message,
      executionTime: executionTime,
      error: error,
      stackTrace: stackTrace,
    );
  }
}

/// 기본 테스트 액션 구현
abstract class BaseTestableAction implements TestableAction {
  @override
  Future<bool> canExecute(WidgetTester tester) async {
    // 기본적으로 항상 실행 가능
    return true;
  }
  
  @override
  Future<bool> verify(WidgetTester tester) async {
    // 기본 검증은 성공으로 가정
    return true;
  }
  
  @override
  Future<bool> recover(WidgetTester tester, dynamic error) async {
    // 기본 복구는 실패로 가정
    return false;
  }
}

/// 탭 액션
class TapAction extends BaseTestableAction {
  final Finder finder;
  final String targetName;
  
  TapAction({
    required this.finder,
    required this.targetName,
  });
  
  @override
  String get name => 'Tap $targetName';
  
  @override
  String get description => 'Tap on $targetName';
  
  @override
  Future<bool> canExecute(WidgetTester tester) async {
    return finder.evaluate().isNotEmpty;
  }
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      await tester.tap(finder);
      await tester.pump();
      
      return ActionResult.success(
        message: 'Successfully tapped $targetName',
        executionTime: stopwatch.elapsed,
      );
    } catch (e, stack) {
      return ActionResult.failure(
        message: 'Failed to tap $targetName: $e',
        executionTime: stopwatch.elapsed,
        error: e,
        stackTrace: stack,
      );
    }
  }
}

/// 텍스트 입력 액션
class EnterTextAction extends BaseTestableAction {
  final Finder finder;
  final String text;
  final String fieldName;
  
  EnterTextAction({
    required this.finder,
    required this.text,
    required this.fieldName,
  });
  
  @override
  String get name => 'Enter text in $fieldName';
  
  @override
  String get description => 'Enter "$text" in $fieldName field';
  
  @override
  Future<bool> canExecute(WidgetTester tester) async {
    return finder.evaluate().isNotEmpty;
  }
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      await tester.enterText(finder, text);
      await tester.pump();
      
      return ActionResult.success(
        message: 'Successfully entered text in $fieldName',
        executionTime: stopwatch.elapsed,
      );
    } catch (e, stack) {
      return ActionResult.failure(
        message: 'Failed to enter text in $fieldName: $e',
        executionTime: stopwatch.elapsed,
        error: e,
        stackTrace: stack,
      );
    }
  }
}

/// 대기 액션
class WaitAction extends BaseTestableAction {
  final Duration duration;
  final String? reason;
  
  WaitAction({
    required this.duration,
    this.reason,
  });
  
  @override
  String get name => 'Wait ${duration.inMilliseconds}ms';
  
  @override
  String get description => reason ?? 'Wait for ${duration.inMilliseconds}ms';
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      await tester.pump(duration);
      
      return ActionResult.success(
        message: 'Waited for ${duration.inMilliseconds}ms',
        executionTime: stopwatch.elapsed,
      );
    } catch (e, stack) {
      return ActionResult.failure(
        message: 'Failed to wait: $e',
        executionTime: stopwatch.elapsed,
        error: e,
        stackTrace: stack,
      );
    }
  }
}

/// 스크롤 액션
class ScrollAction extends BaseTestableAction {
  final Finder scrollable;
  final Finder? target;
  final Offset offset;
  final int maxAttempts;
  
  ScrollAction({
    required this.scrollable,
    this.target,
    this.offset = const Offset(0, -300),
    this.maxAttempts = 10,
  });
  
  @override
  String get name => 'Scroll';
  
  @override
  String get description => target != null 
    ? 'Scroll to find target widget' 
    : 'Scroll by offset ${offset.dx}, ${offset.dy}';
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      if (target != null) {
        // 타겟을 찾을 때까지 스크롤
        for (int i = 0; i < maxAttempts; i++) {
          if (target!.evaluate().isNotEmpty) {
            return ActionResult.success(
              message: 'Found target after $i scrolls',
              executionTime: stopwatch.elapsed,
            );
          }
          
          await tester.drag(scrollable, offset);
          await tester.pump();
        }
        
        return ActionResult.failure(
          message: 'Target not found after $maxAttempts scrolls',
          executionTime: stopwatch.elapsed,
        );
      } else {
        // 단순 스크롤
        await tester.drag(scrollable, offset);
        await tester.pump();
        
        return ActionResult.success(
          message: 'Scrolled by offset',
          executionTime: stopwatch.elapsed,
        );
      }
    } catch (e, stack) {
      return ActionResult.failure(
        message: 'Failed to scroll: $e',
        executionTime: stopwatch.elapsed,
        error: e,
        stackTrace: stack,
      );
    }
  }
}

/// 검증 액션
class VerifyAction extends BaseTestableAction {
  final Future<bool> Function(WidgetTester) verifyFunction;
  final String verificationName;
  
  VerifyAction({
    required this.verifyFunction,
    required this.verificationName,
  });
  
  @override
  String get name => 'Verify $verificationName';
  
  @override
  String get description => 'Verify that $verificationName';
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      final result = await verifyFunction(tester);
      
      if (result) {
        return ActionResult.success(
          message: 'Verification passed: $verificationName',
          executionTime: stopwatch.elapsed,
        );
      } else {
        return ActionResult.failure(
          message: 'Verification failed: $verificationName',
          executionTime: stopwatch.elapsed,
        );
      }
    } catch (e, stack) {
      return ActionResult.failure(
        message: 'Verification error: $e',
        executionTime: stopwatch.elapsed,
        error: e,
        stackTrace: stack,
      );
    }
  }
}

/// 복합 액션 (여러 액션을 순차적으로 실행)
class CompositeAction extends BaseTestableAction {
  final List<TestableAction> actions;
  final String compositeName;
  final bool stopOnFailure;
  
  CompositeAction({
    required this.actions,
    required this.compositeName,
    this.stopOnFailure = true,
  });
  
  @override
  String get name => compositeName;
  
  @override
  String get description => 'Execute ${actions.length} actions for $compositeName';
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    final results = <ActionResult>[];
    
    for (final action in actions) {
      if (!await action.canExecute(tester)) {
        if (stopOnFailure) {
          return ActionResult.failure(
            message: 'Cannot execute action: ${action.name}',
            executionTime: stopwatch.elapsed,
          );
        }
        continue;
      }
      
      final result = await action.execute(tester);
      results.add(result);
      
      if (!result.success && stopOnFailure) {
        return ActionResult.failure(
          message: 'Failed at action: ${action.name} - ${result.message}',
          executionTime: stopwatch.elapsed,
          error: result.error,
          stackTrace: result.stackTrace,
        );
      }
      
      if (!await action.verify(tester) && stopOnFailure) {
        return ActionResult.failure(
          message: 'Verification failed for action: ${action.name}',
          executionTime: stopwatch.elapsed,
        );
      }
    }
    
    final successCount = results.where((r) => r.success).length;
    final totalCount = results.length;
    
    return ActionResult.success(
      message: 'Completed $successCount/$totalCount actions successfully',
      data: results,
      executionTime: stopwatch.elapsed,
      metrics: {
        'total_actions': totalCount,
        'successful_actions': successCount,
        'failed_actions': totalCount - successCount,
      },
    );
  }
}

/// 조건부 액션
class ConditionalAction extends BaseTestableAction {
  final Future<bool> Function(WidgetTester) condition;
  final TestableAction trueAction;
  final TestableAction? falseAction;
  final String conditionName;
  
  ConditionalAction({
    required this.condition,
    required this.trueAction,
    this.falseAction,
    required this.conditionName,
  });
  
  @override
  String get name => 'Conditional: $conditionName';
  
  @override
  String get description => 'Execute action based on condition: $conditionName';
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      final conditionMet = await condition(tester);
      
      if (conditionMet) {
        final result = await trueAction.execute(tester);
        return ActionResult(
          success: result.success,
          message: 'Condition met - ${result.message}',
          data: result.data,
          executionTime: stopwatch.elapsed,
          metrics: result.metrics,
          error: result.error,
          stackTrace: result.stackTrace,
        );
      } else if (falseAction != null) {
        final result = await falseAction!.execute(tester);
        return ActionResult(
          success: result.success,
          message: 'Condition not met - ${result.message}',
          data: result.data,
          executionTime: stopwatch.elapsed,
          metrics: result.metrics,
          error: result.error,
          stackTrace: result.stackTrace,
        );
      } else {
        return ActionResult.success(
          message: 'Condition not met - no action taken',
          executionTime: stopwatch.elapsed,
        );
      }
    } catch (e, stack) {
      return ActionResult.failure(
        message: 'Conditional action error: $e',
        executionTime: stopwatch.elapsed,
        error: e,
        stackTrace: stack,
      );
    }
  }
}

/// 재시도 액션
class RetryAction extends BaseTestableAction {
  final TestableAction action;
  final int maxRetries;
  final Duration retryDelay;
  
  RetryAction({
    required this.action,
    this.maxRetries = 3,
    this.retryDelay = const Duration(seconds: 1),
  });
  
  @override
  String get name => 'Retry ${action.name}';
  
  @override
  String get description => 'Retry ${action.name} up to $maxRetries times';
  
  @override
  Future<ActionResult> execute(WidgetTester tester) async {
    final stopwatch = Stopwatch()..start();
    ActionResult? lastResult;
    
    for (int attempt = 1; attempt <= maxRetries; attempt++) {
      if (!await action.canExecute(tester)) {
        await tester.pump(retryDelay);
        continue;
      }
      
      lastResult = await action.execute(tester);
      
      if (lastResult.success) {
        return ActionResult.success(
          message: 'Succeeded on attempt $attempt - ${lastResult.message}',
          data: lastResult.data,
          executionTime: stopwatch.elapsed,
          metrics: {
            ...?lastResult.metrics,
            'attempts': attempt,
          },
        );
      }
      
      if (attempt < maxRetries) {
        await tester.pump(retryDelay);
        
        // 복구 시도
        if (lastResult.error != null) {
          await action.recover(tester, lastResult.error);
        }
      }
    }
    
    return ActionResult.failure(
      message: 'Failed after $maxRetries attempts - ${lastResult?.message}',
      executionTime: stopwatch.elapsed,
      error: lastResult?.error,
      stackTrace: lastResult?.stackTrace,
    );
  }
}