"""Integration roundtrip tests: parse C headers with libclang -> IR -> LuaJIT FFI output."""

from __future__ import annotations

import textwrap

import pytest

from headerkit.backends import get_backend, is_backend_available
from headerkit.writers.lua import header_to_lua

pytestmark = pytest.mark.skipif(
    not is_backend_available("libclang"),
    reason="libclang backend not available",
)


@pytest.fixture(scope="session")
def backend():
    return get_backend("libclang")


def parse_and_lua(backend, code: str) -> str:
    header = backend.parse(code, "test.h")
    return header_to_lua(header)


class TestLuaOutputStructure:
    """Test the structural wrapper emitted for any lua output."""

    def test_empty_header(self, backend):
        # ESCAPE: test_empty_header
        # CLAIM: An empty C header produces a valid LuaJIT FFI wrapper with no declarations.
        # PATH:  backend.parse("") -> Header with no declarations -> header_to_lua
        # CHECK: Exact output equality verifies each structural element is present.
        # MUTATION: Removing 'local ffi = require("ffi")' would fail the == check.
        # ESCAPE: Nothing reasonable -- full equality leaves no room for structural drift.
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[


            ]]

            return {}
        """)
        result = parse_and_lua(backend, "")
        assert result == expected

    def test_header_path_in_comment(self, backend):
        # ESCAPE: test_header_path_in_comment
        # CLAIM: The lua writer emits the source filename in a comment header line.
        # PATH:  backend.parse(code, "test.h") -> Header(path="test.h") -> header_to_lua
        # CHECK: Exact output equality verifies the source comment line is present.
        # MUTATION: Removing the '-- Source: test.h' line would fail the == check.
        # ESCAPE: Nothing reasonable -- full equality covers all structural lines.
        code = "struct Point { int x; int y; };"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Structs */
            struct {
                int x;
                int y;
            } Point;

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected


class TestLuaStructRoundtrip:
    """Test struct declarations through the libclang -> IR -> lua pipeline."""

    def test_simple_struct(self, backend):
        # ESCAPE: test_simple_struct
        # CLAIM: A named struct with two int fields emits a correct C struct inside ffi.cdef.
        # PATH:  libclang parses struct -> Struct IR -> _struct_to_cdef -> inside ffi.cdef block
        # CHECK: Exact output equality verifies field names, types, struct name, and placement.
        # MUTATION: Swapping field order or mistyping a type name would fail the == check.
        # ESCAPE: Nothing reasonable -- full equality covers field names, types, and structure.
        code = "struct Point { int x; int y; };"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Structs */
            struct {
                int x;
                int y;
            } Point;

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected

    def test_opaque_struct(self, backend):
        # ESCAPE: test_opaque_struct
        # CLAIM: A forward-declared (opaque) struct becomes a typedef forward declaration.
        # PATH:  libclang parses 'struct Handle;' -> Struct(name="Handle", fields=[]) ->
        #        _struct_to_cdef -> "typedef struct Handle Handle;" in opaque section
        # CHECK: Exact output equality verifies the opaque typedef form and section label.
        # MUTATION: Emitting 'struct Handle;' instead of the typedef form would fail.
        # ESCAPE: Nothing reasonable -- full equality covers both the form and section placement.
        code = "struct Handle;"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Opaque types */
            typedef struct Handle Handle;

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected

    def test_anonymous_struct(self, backend):
        # ESCAPE: test_anonymous_struct
        # CLAIM: A typedef of an anonymous struct emits the typedef form with the alias name,
        #        with no "(anonymous" or "(unnamed" noise in the output.
        # PATH:  libclang parses 'typedef struct { int x; } MyPoint;' ->
        #        Struct(name="MyPoint", is_typedef=True) -> _struct_to_cdef -> typedef form
        # CHECK: Exact output equality verifies the typedef form and absence of anonymous markers.
        # MUTATION: Emitting "(anonymous" or "(unnamed" in the name would fail.
        # ESCAPE: Nothing reasonable -- full equality covers the typedef form and name.
        code = "typedef struct { int x; } MyPoint;"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Structs */
            typedef struct {
                int x;
            } MyPoint;

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected
        assert "(anonymous" not in result
        assert "(unnamed" not in result


class TestLuaFunctionRoundtrip:
    """Test function declarations through the libclang -> IR -> lua pipeline."""

    def test_simple_function(self, backend):
        # ESCAPE: test_simple_function
        # CLAIM: A simple two-argument function emits correct C syntax inside ffi.cdef.
        # PATH:  libclang parses 'int add(int a, int b);' -> Function IR ->
        #        _function_to_cdef -> "int add(int a, int b);" in Functions section
        # CHECK: Exact output equality verifies return type, name, parameter types and names.
        # MUTATION: Swapping 'a' and 'b', or emitting 'long' instead of 'int' would fail.
        # ESCAPE: Nothing reasonable -- full equality covers return type, params, name.
        code = "int add(int a, int b);"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Functions */
            int add(int a, int b);

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected

    def test_void_function(self, backend):
        # ESCAPE: test_void_function
        # CLAIM: A void function with no args emits 'void init(void);' inside ffi.cdef.
        # PATH:  libclang parses 'void init(void);' -> Function(return_type=void, params=[]) ->
        #        _function_to_cdef -> "void init(void);"
        # CHECK: Exact output equality verifies void return type and void parameter list.
        # MUTATION: Omitting 'void' in params or return type would fail.
        # ESCAPE: Nothing reasonable -- full equality covers both void positions.
        code = "void init(void);"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Functions */
            void init(void);

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected

    def test_variadic_function(self, backend):
        # ESCAPE: test_variadic_function
        # CLAIM: A variadic function emits '...' in the parameter list inside ffi.cdef.
        # PATH:  libclang parses variadic -> Function(is_variadic=True) ->
        #        _format_params appends "..." -> "int printf(const char * fmt, ...);"
        # CHECK: Exact output equality verifies function name, param type, and '...' placement.
        # MUTATION: Omitting '...' or adding extra parameters would fail.
        # ESCAPE: Nothing reasonable -- full equality covers the variadic marker.
        code = "int printf(const char *fmt, ...);"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Functions */
            int printf(const char * fmt, ...);

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected


class TestLuaEnumRoundtrip:
    """Test enum declarations through the libclang -> IR -> lua pipeline."""

    def test_simple_enum(self, backend):
        # ESCAPE: test_simple_enum
        # CLAIM: A named enum emits a typedef enum block with all values and correct commas.
        # PATH:  libclang parses enum -> Enum IR -> _enum_to_cdef ->
        #        "typedef enum { RED = 0, GREEN = 1, BLUE = 2, } Color;"
        # CHECK: Exact output equality verifies enum name, all three values, and trailing commas.
        # MUTATION: Omitting trailing comma on last value, or wrong value number would fail.
        # ESCAPE: Nothing reasonable -- full equality covers all enum values and formatting.
        code = "enum Color { RED = 0, GREEN = 1, BLUE = 2 };"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Enums */
            typedef enum {
                RED = 0,
                GREEN = 1,
                BLUE = 2,
            } Color;

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected

    def test_enum_values_with_commas(self, backend):
        # ESCAPE: test_enum_values_with_commas
        # CLAIM: All enum values have trailing commas in the output, including the last one.
        # PATH:  _enum_to_cdef always appends ',' to each value line
        # CHECK: Exact equality verifies trailing commas on every value line.
        # MUTATION: Removing the comma from the last value would fail the == check.
        # ESCAPE: Nothing reasonable -- full equality leaves no place to hide missing commas.
        code = "enum Status { OK = 0, FAIL = 1 };"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Enums */
            typedef enum {
                OK = 0,
                FAIL = 1,
            } Status;

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected


class TestLuaMacroRoundtrip:
    """Test macro constant handling through the libclang -> IR -> lua pipeline."""

    def test_integer_macro(self, backend):
        # ESCAPE: test_integer_macro
        # CLAIM: An integer macro becomes 'static const int NAME = VALUE;' inside ffi.cdef,
        #        placed in the Constants section.
        # PATH:  libclang parses #define -> Constant(value=256, is_macro=True) ->
        #        _constant_to_cdef -> "static const int BUF_SIZE = 256;" in constants section
        # CHECK: Exact output equality verifies form, value, section label, and placement.
        # MUTATION: Emitting 'local BUF_SIZE = 256' outside cdef instead of static const
        #           inside cdef would fail.
        # ESCAPE: Nothing reasonable -- full equality covers section and declaration form.
        code = "#define BUF_SIZE 256\nvoid func(void);"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Constants */
            static const int BUF_SIZE = 256;

            /* Functions */
            void func(void);

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected


class TestLuaTypedefRoundtrip:
    """Test typedef declarations through the libclang -> IR -> lua pipeline."""

    def test_simple_typedef(self, backend):
        # ESCAPE: test_simple_typedef
        # CLAIM: A plain typedef emits 'typedef <type> <name>;' in the Structs section.
        # PATH:  libclang parses 'typedef unsigned int uint32;' -> Typedef IR ->
        #        _typedef_to_cdef -> "typedef unsigned int uint32;" in structs section
        # CHECK: Exact output equality verifies typedef form and section placement.
        # MUTATION: Emitting in a different section or omitting 'typedef' keyword would fail.
        # ESCAPE: Nothing reasonable -- full equality catches section and form changes.
        code = "typedef unsigned int uint32;"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Structs */
            typedef unsigned int uint32;

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected

    def test_function_pointer_typedef(self, backend):
        # ESCAPE: test_function_pointer_typedef
        # CLAIM: A function pointer typedef emits the correct typedef form in Callback typedefs
        #        section. Note: libclang does not preserve parameter names in fp typedefs.
        # PATH:  libclang parses 'typedef void (*Callback)(int status);' ->
        #        Typedef(underlying=Pointer(FunctionPointer)) ->
        #        _typedef_to_cdef -> "typedef void (*Callback)(int);" in callbacks section
        # CHECK: Exact output equality verifies the typedef form, name, return type, and section.
        # MUTATION: Emitting in Structs section instead of Callback typedefs would fail.
        # ESCAPE: Nothing reasonable -- full equality covers section and declaration form.
        code = "typedef void (*Callback)(int status);"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Callback typedefs */
            typedef void (*Callback)(int);

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected


class TestLuaCompleteHeader:
    """Test a complete header with multiple declaration types."""

    def test_complete_header(self, backend):
        # ESCAPE: test_complete_header
        # CLAIM: A header with a macro, struct, and function emits all three in their correct
        #        sections in the right order: Constants, Structs, Functions.
        # PATH:  libclang parses all three -> three IR nodes -> header_to_lua emits
        #        Constants section then Structs section then Functions section
        # CHECK: Exact output equality verifies all declarations, all sections, and section order.
        # MUTATION: Swapping section order, omitting a declaration, or wrong struct form would fail.
        # ESCAPE: Nothing reasonable -- full equality covers all three declarations and order.
        code = "#define BUF_SIZE 256\nstruct Buf { char *data; int len; };\nvoid buf_init(struct Buf *b);"
        expected = textwrap.dedent("""\
            -- Auto-generated LuaJIT FFI bindings
            -- Source: test.h
            -- Generated by headerkit

            local ffi = require("ffi")

            ffi.cdef[[

            /* Constants */
            static const int BUF_SIZE = 256;

            /* Structs */
            struct {
                char * data;
                int len;
            } Buf;

            /* Functions */
            void buf_init(struct Buf * b);

            ]]

            return {}
        """)
        result = parse_and_lua(backend, code)
        assert result == expected
