package io.synor.compute;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import static org.junit.jupiter.api.Assertions.*;

@DisplayName("Types Tests")
class TypesTest {

    @Test
    @DisplayName("ProcessorType has all values")
    void testProcessorTypeValues() {
        assertEquals("cpu", ProcessorType.CPU.getValue());
        assertEquals("gpu", ProcessorType.GPU.getValue());
        assertEquals("tpu", ProcessorType.TPU.getValue());
        assertEquals("npu", ProcessorType.NPU.getValue());
        assertEquals("lpu", ProcessorType.LPU.getValue());
        assertEquals("auto", ProcessorType.AUTO.getValue());
    }

    @Test
    @DisplayName("ProcessorType parses from string")
    void testProcessorTypeFromString() {
        assertEquals(ProcessorType.GPU, ProcessorType.fromString("gpu"));
        assertEquals(ProcessorType.TPU, ProcessorType.fromString("tpu"));
        assertEquals(ProcessorType.AUTO, ProcessorType.fromString("invalid"));
    }

    @Test
    @DisplayName("Precision has all values")
    void testPrecisionValues() {
        assertEquals("fp64", Precision.FP64.getValue());
        assertEquals("fp32", Precision.FP32.getValue());
        assertEquals("fp16", Precision.FP16.getValue());
        assertEquals("bf16", Precision.BF16.getValue());
        assertEquals("int8", Precision.INT8.getValue());
        assertEquals("int4", Precision.INT4.getValue());
    }

    @Test
    @DisplayName("Precision parses from string")
    void testPrecisionFromString() {
        assertEquals(Precision.FP16, Precision.fromString("fp16"));
        assertEquals(Precision.BF16, Precision.fromString("bf16"));
        assertEquals(Precision.FP32, Precision.fromString("invalid"));
    }

    @Test
    @DisplayName("Priority has all values")
    void testPriorityValues() {
        assertEquals("critical", Priority.CRITICAL.getValue());
        assertEquals("high", Priority.HIGH.getValue());
        assertEquals("normal", Priority.NORMAL.getValue());
        assertEquals("low", Priority.LOW.getValue());
        assertEquals("background", Priority.BACKGROUND.getValue());
    }

    @Test
    @DisplayName("JobStatus has all values")
    void testJobStatusValues() {
        assertEquals("pending", JobStatus.PENDING.getValue());
        assertEquals("queued", JobStatus.QUEUED.getValue());
        assertEquals("running", JobStatus.RUNNING.getValue());
        assertEquals("completed", JobStatus.COMPLETED.getValue());
        assertEquals("failed", JobStatus.FAILED.getValue());
        assertEquals("cancelled", JobStatus.CANCELLED.getValue());
    }

    @Test
    @DisplayName("JobStatus identifies terminal states")
    void testJobStatusTerminal() {
        assertTrue(JobStatus.COMPLETED.isTerminal());
        assertTrue(JobStatus.FAILED.isTerminal());
        assertTrue(JobStatus.CANCELLED.isTerminal());
        assertFalse(JobStatus.PENDING.isTerminal());
        assertFalse(JobStatus.RUNNING.isTerminal());
    }

    @Test
    @DisplayName("ModelCategory has all values")
    void testModelCategoryValues() {
        assertEquals("llm", ModelCategory.LLM.getValue());
        assertEquals("embedding", ModelCategory.EMBEDDING.getValue());
        assertEquals("image_generation", ModelCategory.IMAGE_GENERATION.getValue());
        assertEquals("custom", ModelCategory.CUSTOM.getValue());
    }

    @Test
    @DisplayName("SynorConfig creates with defaults")
    void testSynorConfigDefaults() {
        SynorConfig config = SynorConfig.builder()
            .apiKey("test-key")
            .build();

        assertEquals("test-key", config.getApiKey());
        assertEquals("https://api.synor.io/compute/v1", config.getBaseUrl());
        assertEquals(30, config.getTimeoutSeconds());
        assertEquals(ProcessorType.AUTO, config.getDefaultProcessor());
        assertEquals(Precision.FP32, config.getDefaultPrecision());
    }

    @Test
    @DisplayName("SynorConfig creates with custom values")
    void testSynorConfigCustom() {
        SynorConfig config = SynorConfig.builder()
            .apiKey("test-key")
            .baseUrl("https://custom.api.com")
            .timeoutSeconds(60)
            .defaultProcessor(ProcessorType.GPU)
            .defaultPrecision(Precision.FP16)
            .debug(true)
            .build();

        assertEquals("https://custom.api.com", config.getBaseUrl());
        assertEquals(60, config.getTimeoutSeconds());
        assertEquals(ProcessorType.GPU, config.getDefaultProcessor());
        assertTrue(config.isDebug());
    }

    @Test
    @DisplayName("MatMulOptions generates correct JSON")
    void testMatMulOptions() {
        MatMulOptions options = MatMulOptions.builder()
            .precision(Precision.FP16)
            .processor(ProcessorType.GPU)
            .priority(Priority.HIGH)
            .build();

        var json = options.toJson();
        assertEquals("fp16", json.get("precision"));
        assertEquals("gpu", json.get("processor"));
        assertEquals("high", json.get("priority"));
    }

    @Test
    @DisplayName("Conv2dOptions has correct defaults")
    void testConv2dOptionsDefaults() {
        Conv2dOptions options = Conv2dOptions.builder().build();

        assertArrayEquals(new int[]{1, 1}, options.getStride());
        assertArrayEquals(new int[]{0, 0}, options.getPadding());
    }

    @Test
    @DisplayName("AttentionOptions generates correct JSON")
    void testAttentionOptions() {
        AttentionOptions options = AttentionOptions.builder()
            .numHeads(8)
            .flash(true)
            .precision(Precision.FP16)
            .build();

        var json = options.toJson();
        assertEquals(8, json.get("num_heads"));
        assertEquals(true, json.get("flash"));
        assertEquals("fp16", json.get("precision"));
    }

    @Test
    @DisplayName("InferenceOptions has correct defaults")
    void testInferenceOptionsDefaults() {
        InferenceOptions options = InferenceOptions.builder().build();

        assertEquals(256, options.getMaxTokens());
        assertEquals(0.7, options.getTemperature(), 0.001);
        assertEquals(0.9, options.getTopP(), 0.001);
        assertEquals(50, options.getTopK());
    }

    @Test
    @DisplayName("InferenceOptions generates correct JSON")
    void testInferenceOptionsJson() {
        InferenceOptions options = InferenceOptions.builder()
            .maxTokens(512)
            .temperature(0.8)
            .topK(40)
            .build();

        var json = options.toJson();
        assertEquals(512, json.get("max_tokens"));
        assertEquals(0.8, ((Number)json.get("temperature")).doubleValue(), 0.001);
        assertEquals(40, json.get("top_k"));
    }

    @Test
    @DisplayName("JobResult identifies success")
    void testJobResultSuccess() {
        JobResult<String> success = JobResult.<String>builder()
            .jobId("job-123")
            .status(JobStatus.COMPLETED)
            .result("output")
            .build();

        assertTrue(success.isSuccess());
        assertFalse(success.isFailed());
    }

    @Test
    @DisplayName("JobResult identifies failure")
    void testJobResultFailure() {
        JobResult<String> failure = JobResult.<String>builder()
            .jobId("job-456")
            .status(JobStatus.FAILED)
            .error("Error message")
            .build();

        assertFalse(failure.isSuccess());
        assertTrue(failure.isFailed());
    }

    @Test
    @DisplayName("ModelInfo formats parameters correctly")
    void testModelInfoFormattedParameters() {
        ModelInfo model = ModelInfo.builder()
            .id("test")
            .name("Test Model")
            .parameters(70000000000L)
            .build();

        assertEquals("70B", model.getFormattedParameters());
    }

    @Test
    @DisplayName("PricingInfo parses from JSON")
    void testPricingInfoFromJson() {
        var json = new java.util.HashMap<String, Object>();
        json.put("processor", "gpu");
        json.put("price_per_second", 0.0001);
        json.put("available_units", 100);
        json.put("utilization_percent", 75.5);

        PricingInfo pricing = PricingInfo.fromJson(json);

        assertEquals("gpu", pricing.getProcessor());
        assertEquals(0.0001, pricing.getPricePerSecond(), 0.00001);
        assertEquals(100, pricing.getAvailableUnits());
    }

    @Test
    @DisplayName("UsageStats parses from JSON")
    void testUsageStatsFromJson() {
        var json = new java.util.HashMap<String, Object>();
        json.put("total_jobs", 1000);
        json.put("completed_jobs", 950);
        json.put("failed_jobs", 50);
        json.put("total_compute_seconds", 3600.0);
        json.put("total_cost", 0.36);

        UsageStats stats = UsageStats.fromJson(json);

        assertEquals(1000, stats.getTotalJobs());
        assertEquals(950, stats.getCompletedJobs());
        assertEquals(3600.0, stats.getTotalComputeSeconds(), 0.001);
    }
}
