package com.correx.core.validation.scene

import com.correx.core.validation.model.ValidationIssue
import com.correx.core.validation.model.ValidationReport
import com.correx.core.validation.model.ValidationSeverity
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class SceneValidatorTest {

    private val validator = SceneValidator()

    private fun ValidationReport.issues(): List<ValidationIssue> =
        sections.flatMap { it.issues }

    private fun ValidationReport.hasIssue(code: String, severity: ValidationSeverity): Boolean =
        issues().any { it.code == code && it.severity == severity }

    @Test
    fun `valid tscn with root and two child nodes and a referenced ext_resource has no issues`() {
        val content = """
            [gd_scene load_steps=3 format=3 uid="uid://abc123"]

            [ext_resource type="Texture2D" path="res://icon.png" id="1"]

            [node name="Root" type="Node2D"]

            [node name="Sprite" type="Sprite2D" parent="."]
            texture = ExtResource("1")

            [node name="Label" type="Label" parent="Sprite"]
            text = "hello"
        """.trimIndent()

        val report = validator.validate(content)

        assertTrue(report.issues().isEmpty(), "expected no issues, got ${report.issues()}")
        assertFalse(report.hasErrors())
    }

    @Test
    fun `malformed unclosed header is flagged as error`() {
        val content = """
            [gd_scene format=3]

            [node name="X"
        """.trimIndent()

        val report = validator.validate(content)

        assertTrue(report.hasIssue("scene.malformed_header", ValidationSeverity.ERROR))
        assertTrue(report.hasErrors())
    }

    @Test
    fun `ExtResource referencing an undeclared id is an unresolved resource error`() {
        val content = """
            [gd_scene format=3]

            [node name="Root" type="Node2D"]
            texture = ExtResource("99")
        """.trimIndent()

        val report = validator.validate(content)

        assertTrue(report.hasIssue("scene.unresolved_resource", ValidationSeverity.ERROR))
    }

    @Test
    fun `SubResource referencing an undeclared id is an unresolved resource error`() {
        val content = """
            [gd_scene format=3]

            [node name="Root" type="Node2D"]
            material = SubResource("MissingMat")
        """.trimIndent()

        val report = validator.validate(content)

        assertTrue(report.hasIssue("scene.unresolved_resource", ValidationSeverity.ERROR))
    }

    @Test
    fun `node with non-existent parent is a bad parent error`() {
        val content = """
            [gd_scene format=3]

            [node name="Root" type="Node2D"]

            [node name="Child" type="Node2D" parent="DoesNotExist"]
        """.trimIndent()

        val report = validator.validate(content)

        assertTrue(report.hasIssue("scene.bad_parent", ValidationSeverity.ERROR))
    }

    @Test
    fun `valid tres resource file with ext and sub resources has no issues`() {
        val content = """
            [gd_resource type="StyleBoxFlat" load_steps=2 format=3 uid="uid://xyz"]

            [ext_resource type="Texture2D" path="res://tex.png" id="1"]

            [sub_resource type="Gradient" id="grad_1"]
            colors = PackedColorArray(1, 1, 1, 1)

            [resource]
            texture = ExtResource("1")
            gradient = SubResource("grad_1")
        """.trimIndent()

        val report = validator.validate(content)

        assertTrue(report.issues().isEmpty(), "expected no issues, got ${report.issues()}")
        assertFalse(report.hasErrors())
    }

    @Test
    fun `unknown section type is a warning`() {
        val content = """
            [gd_scene format=3]

            [bogus_section]

            [node name="Root" type="Node2D"]
        """.trimIndent()

        val report = validator.validate(content)

        assertTrue(report.hasIssue("scene.unknown_section", ValidationSeverity.WARNING))
        // An unknown section is a warning, not an error.
        assertFalse(report.hasErrors())
    }

    @Test
    fun `deeper parent path resolves when the intermediate node was declared`() {
        val content = """
            [gd_scene format=3]

            [node name="Root" type="Node2D"]

            [node name="A" type="Node2D" parent="."]

            [node name="B" type="Node2D" parent="A"]

            [node name="C" type="Node2D" parent="A/B"]
        """.trimIndent()

        val report = validator.validate(content)

        assertFalse(report.hasIssue("scene.bad_parent", ValidationSeverity.ERROR))
        assertTrue(report.issues().isEmpty())
    }
}
