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

void main() {
  group('Precision Enum', () {
    test('has all expected values', () {
      expect(Precision.values.length, equals(6));
      expect(Precision.fp64.value, equals('fp64'));
      expect(Precision.fp32.value, equals('fp32'));
      expect(Precision.fp16.value, equals('fp16'));
      expect(Precision.bf16.value, equals('bf16'));
      expect(Precision.int8.value, equals('int8'));
      expect(Precision.int4.value, equals('int4'));
    });

    test('parses from string', () {
      expect(Precision.fromString('fp16'), equals(Precision.fp16));
      expect(Precision.fromString('bf16'), equals(Precision.bf16));
      expect(Precision.fromString('invalid'), equals(Precision.fp32)); // default
    });
  });

  group('ProcessorType Enum', () {
    test('has all expected values', () {
      expect(ProcessorType.values.length, equals(10));
      expect(ProcessorType.cpu.value, equals('cpu'));
      expect(ProcessorType.gpu.value, equals('gpu'));
      expect(ProcessorType.tpu.value, equals('tpu'));
      expect(ProcessorType.npu.value, equals('npu'));
      expect(ProcessorType.lpu.value, equals('lpu'));
      expect(ProcessorType.auto.value, equals('auto'));
    });

    test('parses from string', () {
      expect(ProcessorType.fromString('gpu'), equals(ProcessorType.gpu));
      expect(ProcessorType.fromString('invalid'), equals(ProcessorType.auto));
    });
  });

  group('Priority Enum', () {
    test('has all expected values', () {
      expect(Priority.values.length, equals(4));
      expect(Priority.low.value, equals('low'));
      expect(Priority.normal.value, equals('normal'));
      expect(Priority.high.value, equals('high'));
      expect(Priority.critical.value, equals('critical'));
    });

    test('parses from string', () {
      expect(Priority.fromString('high'), equals(Priority.high));
      expect(Priority.fromString('invalid'), equals(Priority.normal));
    });
  });

  group('JobStatus Enum', () {
    test('has all expected values', () {
      expect(JobStatus.values.length, equals(6));
      expect(JobStatus.pending.value, equals('pending'));
      expect(JobStatus.queued.value, equals('queued'));
      expect(JobStatus.running.value, equals('running'));
      expect(JobStatus.completed.value, equals('completed'));
      expect(JobStatus.failed.value, equals('failed'));
      expect(JobStatus.cancelled.value, equals('cancelled'));
    });

    test('identifies terminal states', () {
      expect(JobStatus.completed.isTerminal, isTrue);
      expect(JobStatus.failed.isTerminal, isTrue);
      expect(JobStatus.cancelled.isTerminal, isTrue);
      expect(JobStatus.pending.isTerminal, isFalse);
      expect(JobStatus.running.isTerminal, isFalse);
    });
  });

  group('BalancingStrategy Enum', () {
    test('has all expected values', () {
      expect(BalancingStrategy.speed.value, equals('speed'));
      expect(BalancingStrategy.cost.value, equals('cost'));
      expect(BalancingStrategy.energy.value, equals('energy'));
      expect(BalancingStrategy.balanced.value, equals('balanced'));
    });
  });

  group('DType Enum', () {
    test('has all expected values', () {
      expect(DType.float64.value, equals('float64'));
      expect(DType.float32.value, equals('float32'));
      expect(DType.int8.value, equals('int8'));
      expect(DType.bool_.value, equals('bool'));
    });
  });

  group('ModelCategory Enum', () {
    test('has all expected values', () {
      expect(ModelCategory.llm.value, equals('llm'));
      expect(ModelCategory.embedding.value, equals('embedding'));
      expect(ModelCategory.imageGeneration.value, equals('image_generation'));
      expect(ModelCategory.custom.value, equals('custom'));
    });
  });

  group('SynorConfig', () {
    test('creates with required fields', () {
      const config = SynorConfig(apiKey: 'test-key');

      expect(config.apiKey, equals('test-key'));
      expect(config.baseUrl, equals('https://compute.synor.io'));
      expect(config.timeout, equals(const Duration(seconds: 30)));
      expect(config.maxRetries, equals(3));
      expect(config.defaultProcessor, equals(ProcessorType.auto));
      expect(config.defaultPrecision, equals(Precision.fp32));
    });

    test('creates with custom values', () {
      const config = SynorConfig(
        apiKey: 'test-key',
        baseUrl: 'https://custom.api.com',
        timeout: Duration(seconds: 60),
        maxRetries: 5,
        defaultProcessor: ProcessorType.gpu,
        defaultPrecision: Precision.fp16,
      );

      expect(config.baseUrl, equals('https://custom.api.com'));
      expect(config.timeout, equals(const Duration(seconds: 60)));
      expect(config.maxRetries, equals(5));
    });

    test('copyWith updates fields', () {
      const original = SynorConfig(apiKey: 'test-key');
      final updated = original.copyWith(
        timeout: const Duration(seconds: 60),
        defaultProcessor: ProcessorType.tpu,
      );

      expect(updated.apiKey, equals('test-key'));
      expect(updated.timeout, equals(const Duration(seconds: 60)));
      expect(updated.defaultProcessor, equals(ProcessorType.tpu));
    });
  });

  group('MatMulOptions', () {
    test('creates with defaults', () {
      const options = MatMulOptions();

      expect(options.precision, isNull);
      expect(options.processor, isNull);
      expect(options.transposeA, isFalse);
      expect(options.transposeB, isFalse);
    });

    test('toJson generates correct output', () {
      const options = MatMulOptions(
        precision: Precision.fp16,
        processor: ProcessorType.gpu,
        transposeA: true,
      );

      final json = options.toJson();

      expect(json['precision'], equals('fp16'));
      expect(json['processor'], equals('gpu'));
      expect(json['transpose_a'], isTrue);
      expect(json['transpose_b'], isFalse);
    });
  });

  group('Conv2dOptions', () {
    test('creates with defaults', () {
      const options = Conv2dOptions();

      expect(options.kernel, equals([3, 3]));
      expect(options.stride, equals([1, 1]));
      expect(options.padding, equals([0, 0]));
      expect(options.groups, equals(1));
    });

    test('toJson generates correct output', () {
      const options = Conv2dOptions(
        kernel: [5, 5],
        stride: [2, 2],
        padding: [2, 2],
      );

      final json = options.toJson();

      expect(json['kernel'], equals([5, 5]));
      expect(json['stride'], equals([2, 2]));
      expect(json['padding'], equals([2, 2]));
    });
  });

  group('AttentionOptions', () {
    test('creates with required fields', () {
      const options = AttentionOptions(numHeads: 8);

      expect(options.numHeads, equals(8));
      expect(options.causal, isFalse);
      expect(options.scale, isNull);
    });

    test('toJson generates correct output', () {
      const options = AttentionOptions(
        numHeads: 12,
        scale: 0.125,
        causal: true,
        precision: Precision.fp16,
      );

      final json = options.toJson();

      expect(json['num_heads'], equals(12));
      expect(json['scale'], equals(0.125));
      expect(json['causal'], isTrue);
      expect(json['precision'], equals('fp16'));
    });
  });

  group('InferenceOptions', () {
    test('creates with defaults', () {
      const options = InferenceOptions();

      expect(options.maxTokens, equals(256));
      expect(options.temperature, equals(0.7));
      expect(options.topP, equals(1.0));
      expect(options.stream, isFalse);
    });

    test('toJson generates correct output', () {
      const options = InferenceOptions(
        maxTokens: 512,
        temperature: 0.9,
        topK: 50,
        stream: true,
      );

      final json = options.toJson();

      expect(json['max_tokens'], equals(512));
      expect(json['temperature'], equals(0.9));
      expect(json['top_k'], equals(50));
      expect(json['stream'], isTrue);
    });
  });

  group('PricingInfo', () {
    test('parses from JSON', () {
      final json = {
        'processor': 'gpu',
        'price_per_second': 0.0001,
        'price_per_gflop': 0.000001,
        'available_units': 100,
        'utilization_percent': 75.5,
        'region': 'us-east-1',
      };

      final pricing = PricingInfo.fromJson(json);

      expect(pricing.processor, equals(ProcessorType.gpu));
      expect(pricing.pricePerSecond, equals(0.0001));
      expect(pricing.availableUnits, equals(100));
      expect(pricing.utilizationPercent, equals(75.5));
    });
  });

  group('UsageStats', () {
    test('parses from JSON', () {
      final json = {
        'total_jobs': 1000,
        'completed_jobs': 950,
        'failed_jobs': 50,
        'total_compute_seconds': 3600.0,
        'total_cost': 0.36,
        'cost_by_processor': {
          'gpu': 0.30,
          'tpu': 0.06,
        },
      };

      final stats = UsageStats.fromJson(json);

      expect(stats.totalJobs, equals(1000));
      expect(stats.completedJobs, equals(950));
      expect(stats.failedJobs, equals(50));
      expect(stats.totalComputeSeconds, equals(3600.0));
      expect(stats.costByProcessor[ProcessorType.gpu], equals(0.30));
    });
  });

  group('ModelInfo', () {
    test('parses from JSON', () {
      final json = {
        'id': 'llama-3-70b',
        'name': 'Llama 3 70B',
        'description': 'Large language model',
        'category': 'llm',
        'cid': 'QmTest...',
        'format': 'safetensors',
        'size_bytes': 140000000000,
        'parameters': 70000000000,
        'supported_precisions': ['fp16', 'bf16', 'fp32'],
        'recommended_processor': 'gpu',
        'context_length': 8192,
        'license': 'llama3',
        'provider': 'meta',
        'version': '1.0.0',
        'is_public': true,
      };

      final model = ModelInfo.fromJson(json);

      expect(model.id, equals('llama-3-70b'));
      expect(model.category, equals(ModelCategory.llm));
      expect(model.parameters, equals(70000000000));
      expect(model.contextLength, equals(8192));
    });

    test('formats parameters correctly', () {
      const model = ModelInfo(
        id: 'test',
        name: 'Test',
        description: 'Test',
        category: ModelCategory.llm,
        cid: 'QmTest',
        format: ModelFormat.onnx,
        sizeBytes: 1000,
        parameters: 70000000000,
        supportedPrecisions: [Precision.fp16],
        recommendedProcessor: ProcessorType.gpu,
        license: 'MIT',
        provider: 'test',
        version: '1.0',
        isPublic: true,
      );

      expect(model.formattedParameters, equals('70.0B'));
    });
  });

  group('TrainingOptions', () {
    test('creates with defaults', () {
      const options = TrainingOptions();

      expect(options.framework, equals(MlFramework.pytorch));
      expect(options.epochs, equals(1));
      expect(options.batchSize, equals(32));
      expect(options.learningRate, equals(0.001));
    });

    test('toJson generates correct output', () {
      const options = TrainingOptions(
        framework: MlFramework.jax,
        epochs: 10,
        batchSize: 64,
        learningRate: 0.0001,
        distributed: true,
      );

      final json = options.toJson();

      expect(json['framework'], equals('jax'));
      expect(json['epochs'], equals(10));
      expect(json['batch_size'], equals(64));
      expect(json['distributed'], isTrue);
    });
  });

  group('TrainingResult', () {
    test('parses from JSON', () {
      final json = {
        'job_id': 'train-123',
        'model_cid': 'QmTrained...',
        'epochs': 10,
        'final_loss': 0.05,
        'metrics': {'accuracy': 0.95, 'f1': 0.94},
        'duration_ms': 3600000,
        'cost': 1.50,
        'checkpoint_cids': ['QmChk1', 'QmChk2'],
      };

      final result = TrainingResult.fromJson(json);

      expect(result.jobId, equals('train-123'));
      expect(result.epochs, equals(10));
      expect(result.finalLoss, equals(0.05));
      expect(result.metrics['accuracy'], equals(0.95));
    });
  });

  group('DatasetUploadOptions', () {
    test('creates with defaults', () {
      const options = DatasetUploadOptions(name: 'test-dataset');

      expect(options.name, equals('test-dataset'));
      expect(options.format, equals(DatasetFormat.jsonl));
      expect(options.type, equals(DatasetType.textCompletion));
      expect(options.isPublic, isFalse);
    });

    test('toJson generates correct output', () {
      const options = DatasetUploadOptions(
        name: 'my-dataset',
        description: 'Test dataset',
        format: DatasetFormat.parquet,
        type: DatasetType.instructionTuning,
        isPublic: true,
      );

      final json = options.toJson();

      expect(json['name'], equals('my-dataset'));
      expect(json['format'], equals('parquet'));
      expect(json['type'], equals('instruction_tuning'));
      expect(json['is_public'], isTrue);
    });
  });

  group('TrainingProgress', () {
    test('parses from JSON', () {
      final json = {
        'job_id': 'train-123',
        'epoch': 5,
        'total_epochs': 10,
        'step': 500,
        'total_steps': 1000,
        'loss': 0.15,
        'metrics': {'accuracy': 0.85},
        'learning_rate': 0.0001,
        'samples_per_second': 100,
      };

      final progress = TrainingProgress.fromJson(json);

      expect(progress.epoch, equals(5));
      expect(progress.progress, equals(0.5));
      expect(progress.progressText, contains('Epoch 6/10'));
    });
  });

  group('SynorException', () {
    test('creates with message', () {
      const exception = SynorException('Test error', code: 'ERR_TEST');

      expect(exception.message, equals('Test error'));
      expect(exception.code, equals('ERR_TEST'));
      expect(exception.toString(), contains('Test error'));
    });

    test('parses from JSON', () {
      final json = {
        'message': 'API Error',
        'code': 'API_ERR',
        'status_code': 500,
      };

      final exception = SynorException.fromJson(json);

      expect(exception.message, equals('API Error'));
      expect(exception.statusCode, equals(500));
    });
  });
}
