package io.github.picocrypt_ng.picocrypt_ng

import org.junit.Assert.*
import org.junit.Test
import org.json.JSONObject
import org.json.JSONArray

/**
 * Unit tests for GoBridge class.
 * 
 * Note: GoBridge uses mobile.Mobile which is a generated class from Go mobile bindings.
 * Full integration testing requires the Go mobile AAR to be built and should be done
 * in instrumented tests. These unit tests focus on error handling and conversion logic.
 * 
 * Some tests may fail if Go mobile bindings are not available - this is expected in
 * unit test environments where the AAR may not be built.
 */
class GoBridgeTest {
    
    /**
     * Checks if Go mobile bindings are available.
     */
    private fun isGoMobileAvailable(): Boolean {
        return try {
            Class.forName("mobile.Mobile")
            true
        } catch (e: ClassNotFoundException) {
            false
        } catch (e: UnsatisfiedLinkError) {
            false
        } catch (e: NoClassDefFoundError) {
            false
        }
    }
    
    @Test
    fun `startOperation returns operation ID on success`() {
        // This test requires the Go mobile bindings to be available
        // In a real scenario, this would be an instrumented test
        // For unit tests, we verify the fallback behavior or handle missing bindings
        try {
            val operationId = GoBridge.startOperation()
            assertNotNull("Operation ID should not be null", operationId)
            assertTrue("Operation ID should not be empty", operationId.isNotEmpty())
        } catch (e: UnsatisfiedLinkError) {
            // Go mobile bindings not available - this is expected in unit tests
            // Skip this test when bindings aren't available
        } catch (e: NoClassDefFoundError) {
            // Go mobile bindings not available - this is expected in unit tests
            // Skip this test when bindings aren't available
        }
    }
    
    @Test
    fun `startOperation provides fallback ID on exception`() {
        // The fallback ID format is "op_${System.currentTimeMillis()}"
        // We can't easily test this without mocking Mobile, but we verify
        // the method doesn't throw exceptions (except for missing bindings)
        try {
            val operationId = GoBridge.startOperation()
            assertNotNull(operationId)
        } catch (e: UnsatisfiedLinkError) {
            // Go mobile bindings not available - this is expected in unit tests
        } catch (e: NoClassDefFoundError) {
            // Go mobile bindings not available - this is expected in unit tests
        } catch (e: Exception) {
            fail("startOperation should not throw exceptions (except missing bindings), but got: ${e.message}")
        }
    }
    
    @Test
    fun `detectOperation returns error on exception`() {
        // Test with invalid file path that should cause an exception
        try {
            val result = GoBridge.detectOperation("")
            
            // Result should be either success or failure, but not throw
            assertNotNull("Result should not be null", result)
            
            // If it fails, should be a proper AppError
            result.onFailure { error ->
                assertTrue("Error should be AppError", error is AppError)
                assertTrue("Error should be GenericOperation", error is AppError.OperationError.GenericOperation)
            }
        } catch (e: UnsatisfiedLinkError) {
            // Go mobile bindings not available - this is expected in unit tests
        } catch (e: NoClassDefFoundError) {
            // Go mobile bindings not available - this is expected in unit tests
        }
    }
    
    @Test
    fun `startEncrypt JSON structure is correct`() {
        // This test doesn't require Go mobile bindings - it just tests JSON structure
        // Test that the JSON structure built by startEncrypt is correct
        // We can't easily test the full flow without Mobile, but we can verify
        // the JSON building logic by examining what would be created
        
        // Skip if Go mobile bindings cause class loading issues
        if (!isGoMobileAvailable()) {
            return
        }
        
        val operationID = "test_op_123"
        val inputFile = "/path/to/input.txt"
        val outputFile = "/path/to/output.pcv"
        val password = "testpassword".toCharArray()
        val options = EncryptOptions(
            comments = "Test comments",
            paranoid = true,
            reedSolomon = true,
            deniability = false,
            compress = false,
            keyfiles = listOf("keyfile1", "keyfile2"),
            keyfileOrdered = true
        )
        
        // Build the expected JSON structure
        val expectedJson = JSONObject().apply {
            put("operationID", operationID)
            put("inputFile", inputFile)
            put("outputFile", outputFile)
            put("password", String(password))
            put("comments", options.comments)
            put("keyfiles", JSONArray().apply {
                options.keyfiles.forEach { put(it) }
            })
            put("paranoid", options.paranoid)
            put("reedSolomon", options.reedSolomon)
            put("deniability", options.deniability)
            put("compress", options.compress)
            put("keyfileOrdered", options.keyfileOrdered)
        }
        
        // Verify JSON structure
        assertEquals(operationID, expectedJson.getString("operationID"))
        assertEquals(inputFile, expectedJson.getString("inputFile"))
        assertEquals(outputFile, expectedJson.getString("outputFile"))
        assertEquals(String(password), expectedJson.getString("password"))
        assertEquals(options.comments, expectedJson.getString("comments"))
        assertEquals(options.paranoid, expectedJson.getBoolean("paranoid"))
        assertEquals(options.reedSolomon, expectedJson.getBoolean("reedSolomon"))
        assertEquals(options.deniability, expectedJson.getBoolean("deniability"))
        assertEquals(options.compress, expectedJson.getBoolean("compress"))
        assertEquals(options.keyfileOrdered, expectedJson.getBoolean("keyfileOrdered"))
        
        val keyfilesArray = expectedJson.getJSONArray("keyfiles")
        assertEquals(2, keyfilesArray.length())
        assertEquals("keyfile1", keyfilesArray.getString(0))
        assertEquals("keyfile2", keyfilesArray.getString(1))
    }
    
    @Test
    fun `startDecrypt JSON structure is correct`() {
        val operationID = "test_op_123"
        val inputFile = "/path/to/input.pcv"
        val outputFile = "/path/to/output.txt"
        val password = "testpassword".toCharArray()
        val options = DecryptOptions(
            keyfiles = listOf("keyfile1"),
            forceDecrypt = true,
            verifyFirst = false,
            autoUnzip = true,
            sameLevel = false,
            recombine = false,
            deniability = false
        )
        
        // Build the expected JSON structure
        val expectedJson = JSONObject().apply {
            put("operationID", operationID)
            put("inputFile", inputFile)
            put("outputFile", outputFile)
            put("password", String(password))
            put("keyfiles", JSONArray().apply {
                options.keyfiles.forEach { put(it) }
            })
            put("forceDecrypt", options.forceDecrypt)
            put("verifyFirst", options.verifyFirst)
            put("autoUnzip", options.autoUnzip)
            put("sameLevel", options.sameLevel)
            put("recombine", options.recombine)
            put("deniability", options.deniability)
        }
        
        // Verify JSON structure
        assertEquals(operationID, expectedJson.getString("operationID"))
        assertEquals(inputFile, expectedJson.getString("inputFile"))
        assertEquals(outputFile, expectedJson.getString("outputFile"))
        assertEquals(String(password), expectedJson.getString("password"))
        assertEquals(options.forceDecrypt, expectedJson.getBoolean("forceDecrypt"))
        assertEquals(options.verifyFirst, expectedJson.getBoolean("verifyFirst"))
        assertEquals(options.autoUnzip, expectedJson.getBoolean("autoUnzip"))
        assertEquals(options.sameLevel, expectedJson.getBoolean("sameLevel"))
        assertEquals(options.recombine, expectedJson.getBoolean("recombine"))
        assertEquals(options.deniability, expectedJson.getBoolean("deniability"))
    }
    
    @Test
    fun `startEncrypt converts empty error message to success`() {
        // When Mobile.startEncrypt returns empty string, it should be success
        // This is tested by the actual implementation logic
        // We verify the error handling path works correctly
        val password = "test".toCharArray()
        val options = EncryptOptions()
        
        try {
            // This will fail if Mobile throws, but we're testing the error conversion logic
            val result = GoBridge.startEncrypt(
                "op_123",
                "/input",
                "/output",
                password,
                options
            )
            
            // Result should not throw, and if it fails, should be a proper AppError
            result.onFailure { error ->
                assertTrue("Error should be AppError", error is AppError)
            }
        } catch (e: UnsatisfiedLinkError) {
            // Go mobile bindings not available - this is expected in unit tests
        } catch (e: NoClassDefFoundError) {
            // Go mobile bindings not available - this is expected in unit tests
        }
    }
    
    @Test
    fun `startDecrypt converts empty error message to success`() {
        val password = "test".toCharArray()
        val options = DecryptOptions()
        
        try {
            val result = GoBridge.startDecrypt(
                "op_123",
                "/input.pcv",
                "/output",
                password,
                options
            )
            
            result.onFailure { error ->
                assertTrue("Error should be AppError", error is AppError)
            }
        } catch (e: UnsatisfiedLinkError) {
            // Go mobile bindings not available - this is expected in unit tests
        } catch (e: NoClassDefFoundError) {
            // Go mobile bindings not available - this is expected in unit tests
        }
    }
    
    @Test
    fun `getDecryptionInfo parses JSON correctly`() {
        // Test JSON parsing logic
        // This test doesn't require Go mobile bindings - it just tests JSON parsing
        
        // Skip if Go mobile bindings cause class loading issues
        if (!isGoMobileAvailable()) {
            return
        }
        
        val jsonString = """
        {
            "keyfilesRequired": true,
            "keyfileOrdered": true,
            "reedSolomon": true,
            "deniability": false,
            "paranoid": true,
            "comments": "Test comments",
            "readable": true
        }
        """.trimIndent()
        
        val json = JSONObject(jsonString)
        val decryptionInfo = DecryptionInfo(
            keyfilesRequired = json.getBoolean("keyfilesRequired"),
            keyfileOrdered = json.getBoolean("keyfileOrdered"),
            reedSolomon = json.getBoolean("reedSolomon"),
            deniability = json.getBoolean("deniability"),
            paranoid = json.getBoolean("paranoid"),
            comments = json.getString("comments"),
            readable = json.getBoolean("readable")
        )
        
        assertEquals(true, decryptionInfo.keyfilesRequired)
        assertEquals(true, decryptionInfo.keyfileOrdered)
        assertEquals(true, decryptionInfo.reedSolomon)
        assertEquals(false, decryptionInfo.deniability)
        assertEquals(true, decryptionInfo.paranoid)
        assertEquals("Test comments", decryptionInfo.comments)
        assertEquals(true, decryptionInfo.readable)
    }
    
    @Test
    fun `getDecryptionInfo handles missing fields with defaults`() {
        // Test that missing fields are handled (though the actual implementation
        // would throw JSONException, we verify the structure)
        val minimalJson = """
        {
            "keyfilesRequired": false,
            "keyfileOrdered": false,
            "reedSolomon": false,
            "deniability": false,
            "paranoid": false,
            "comments": "",
            "readable": true
        }
        """.trimIndent()
        
        val json = JSONObject(minimalJson)
        val decryptionInfo = DecryptionInfo(
            keyfilesRequired = json.getBoolean("keyfilesRequired"),
            keyfileOrdered = json.getBoolean("keyfileOrdered"),
            reedSolomon = json.getBoolean("reedSolomon"),
            deniability = json.getBoolean("deniability"),
            paranoid = json.getBoolean("paranoid"),
            comments = json.getString("comments"),
            readable = json.getBoolean("readable")
        )
        
        assertNotNull(decryptionInfo)
    }
    
    @Test
    fun `EncryptOptions has correct defaults`() {
        val options = EncryptOptions()
        assertEquals("", options.comments)
        assertEquals(false, options.paranoid)
        assertEquals(false, options.reedSolomon)
        assertEquals(false, options.deniability)
        assertEquals(false, options.compress)
        assertEquals(emptyList<String>(), options.keyfiles)
        assertEquals(false, options.keyfileOrdered)
    }
    
    @Test
    fun `DecryptOptions has correct defaults`() {
        val options = DecryptOptions()
        assertEquals(emptyList<String>(), options.keyfiles)
        assertEquals(false, options.forceDecrypt)
        assertEquals(false, options.verifyFirst)
        assertEquals(false, options.autoUnzip)
        assertEquals(false, options.sameLevel)
        assertEquals(false, options.recombine)
        assertEquals(false, options.deniability)
    }
    
    @Test
    fun `ProgressState structure is correct`() {
        val progressState = ProgressState(
            status = "Processing",
            progress = 0.5f,
            info = "Encrypting file...",
            done = false
        )
        
        assertEquals("Processing", progressState.status)
        assertEquals(0.5f, progressState.progress, 0.001f)
        assertEquals("Encrypting file...", progressState.info)
        assertEquals(false, progressState.done)
    }
}

