"""Tests for StructureGenerator with concept-to-concept references.

This module tests the generation of Pydantic classes that have fields
referencing other concepts (type = "concept" with concept_ref).
"""

from pipelex.core.concepts.concept_structure_blueprint import ConceptStructureBlueprint, ConceptStructureBlueprintFieldType
from pipelex.core.concepts.structure_generation.generator import ConceptClassInfo, StructureGenerator
from pipelex.core.stuffs.structured_content import StructuredContent

AUTOGEN_HEADER = '''\
"""
AUTOGENERATED CODE - DO NOT EDIT

If you want to customize this structure:
  1. Copy this file to your own module
  2. Remove the 'structure' or 'refines' declaration from the concept in the MTHDS file
     and declare it in inline mode (see https://docs.pipelex.com/latest/building-methods/concepts/define_your_concepts/#basic-concept-definition)
  3. Make sure your custom class is importable and registered

To regenerate: pipelex build structures <target_directory>
"""

'''

STANDARD_IMPORTS = """\
from enum import Enum
from pipelex.core.stuffs.structured_content import StructuredContent
from pydantic import Field
from typing import Optional, List, Dict, Any, Literal
"""


class TestStructureGeneratorConceptRefs:
    """Test StructureGenerator with concept type fields."""

    def test_single_concept_ref_field_uses_forward_reference(self):
        """Test generation of a class with a single concept reference field uses forward reference."""
        structure_blueprint = {
            "customer": ConceptStructureBlueprint(
                description="The customer for this invoice",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="myapp.Customer",
                required=True,
            ),
            "total": ConceptStructureBlueprint(
                description="Invoice total",
                type=ConceptStructureBlueprintFieldType.NUMBER,
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("Invoice", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class Invoice(StructuredContent):
    """Generated Invoice class"""

    customer: "myapp__Customer" = Field(..., description="The customer for this invoice")
    total: float = Field(..., description="Invoice total")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_list_of_concepts_uses_forward_reference(self):
        """Test generation of a class with a list of concept references uses forward reference."""
        structure_blueprint = {
            "line_items": ConceptStructureBlueprint(
                description="List of line items",
                type=ConceptStructureBlueprintFieldType.LIST,
                item_type="concept",
                item_concept_ref="myapp.LineItem",
                required=True,
            ),
            "total": ConceptStructureBlueprint(
                description="Invoice total",
                type=ConceptStructureBlueprintFieldType.NUMBER,
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("Invoice", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class Invoice(StructuredContent):
    """Generated Invoice class"""

    line_items: List["myapp__LineItem"] = Field(..., description="List of line items")
    total: float = Field(..., description="Invoice total")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_optional_concept_ref_field_uses_forward_reference(self):
        """Test generation of an optional concept reference field uses forward reference."""
        structure_blueprint = {
            "parent": ConceptStructureBlueprint(
                description="Optional parent reference",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="myapp.Category",
                required=False,
            ),
            "name": ConceptStructureBlueprint(
                description="Category name",
                type=ConceptStructureBlueprintFieldType.TEXT,
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("CategoryNode", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class CategoryNode(StructuredContent):
    """Generated CategoryNode class"""

    parent: Optional["myapp__Category"] = Field(default=None, description="Optional parent reference")
    name: str = Field(..., description="Category name")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_optional_list_of_concepts_uses_forward_reference(self):
        """Test generation of an optional list of concept references uses forward reference."""
        structure_blueprint = {
            "children": ConceptStructureBlueprint(
                description="Optional child categories",
                type=ConceptStructureBlueprintFieldType.LIST,
                item_type="concept",
                item_concept_ref="myapp.Category",
                required=False,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("Parent", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class Parent(StructuredContent):
    """Generated Parent class"""

    children: Optional[List["myapp__Category"]] = Field(default=None, description="Optional child categories")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_cross_domain_concept_ref_uses_forward_reference(self):
        """Test generation with cross-domain concept references uses forward references."""
        structure_blueprint = {
            "customer": ConceptStructureBlueprint(
                description="Customer from CRM domain",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="crm.Customer",
                required=True,
            ),
            "products": ConceptStructureBlueprint(
                description="Products from inventory domain",
                type=ConceptStructureBlueprintFieldType.LIST,
                item_type="concept",
                item_concept_ref="inventory.Product",
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("Order", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class Order(StructuredContent):
    """Generated Order class"""

    customer: "crm__Customer" = Field(..., description="Customer from CRM domain")
    products: List["inventory__Product"] = Field(..., description="Products from inventory domain")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_concept_ref_without_mapping_uses_forward_reference(self):
        """Test that concept_ref without mapping uses forward reference."""
        structure_blueprint = {
            "item": ConceptStructureBlueprint(
                description="An item reference",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="unknown_domain.SomeItem",
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("Container", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class Container(StructuredContent):
    """Generated Container class"""

    item: "unknown_domain__SomeItem" = Field(..., description="An item reference")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_mixed_concept_and_primitive_fields_uses_forward_references(self):
        """Test generation with a mix of concept references and primitive fields uses forward references."""
        structure_blueprint = {
            "id": ConceptStructureBlueprint(
                description="Unique identifier",
                type=ConceptStructureBlueprintFieldType.INTEGER,
                required=True,
            ),
            "name": ConceptStructureBlueprint(
                description="Name",
                type=ConceptStructureBlueprintFieldType.TEXT,
                required=True,
            ),
            "owner": ConceptStructureBlueprint(
                description="Owner reference",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="myapp.User",
                required=False,
            ),
            "tags": ConceptStructureBlueprint(
                description="Tags",
                type=ConceptStructureBlueprintFieldType.LIST,
                item_type="text",
                required=False,
            ),
            "related_items": ConceptStructureBlueprint(
                description="Related items",
                type=ConceptStructureBlueprintFieldType.LIST,
                item_type="concept",
                item_concept_ref="myapp.Item",
                required=False,
            ),
            "metadata": ConceptStructureBlueprint(
                description="Metadata",
                type=ConceptStructureBlueprintFieldType.DICT,
                key_type="text",
                value_type="text",
                required=False,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("ComplexEntity", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class ComplexEntity(StructuredContent):
    """Generated ComplexEntity class"""

    id: int = Field(..., description="Unique identifier")
    name: str = Field(..., description="Name")
    owner: Optional["myapp__User"] = Field(default=None, description="Owner reference")
    tags: Optional[List[str]] = Field(default=None, description="Tags")
    related_items: Optional[List["myapp__Item"]] = Field(default=None, description="Related items")
    metadata: Optional[Dict[str, str]] = Field(default=None, description="Metadata")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_native_concept_ref_generates_import_and_uses_content_class(self):
        """Test generation with native concept references (e.g., native.Html) generates proper import."""
        structure_blueprint = {
            "html_report": ConceptStructureBlueprint(
                description="HTML report content",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="native.Html",
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("ReportWrapper", structure_blueprint)

        # Native concepts should generate imports and use the Content class name
        assert "from pipelex.core.stuffs.html_content import HtmlContent" in generated_code
        assert "html_report: HtmlContent = Field(...," in generated_code
        assert issubclass(generated_class, StructuredContent)

    def test_native_text_concept_ref_generates_import(self):
        """Test generation with native.Text concept reference generates proper import."""
        structure_blueprint = {
            "text_content": ConceptStructureBlueprint(
                description="Text content reference",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="native.Text",
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("TextWrapper", structure_blueprint)

        # Native concepts should generate imports and use the Content class name
        assert "from pipelex.core.stuffs.text_content import TextContent" in generated_code
        assert "text_content: TextContent = Field(...," in generated_code
        assert issubclass(generated_class, StructuredContent)

    def test_list_of_native_concepts_generates_import(self):
        """Test generation with list of native concept references generates proper import."""
        structure_blueprint = {
            "images": ConceptStructureBlueprint(
                description="List of images",
                type=ConceptStructureBlueprintFieldType.LIST,
                item_type="concept",
                item_concept_ref="native.Image",
                required=True,
            ),
        }

        generator = StructureGenerator()
        generated_code, generated_class = generator.generate_from_structure_blueprint("ImageGallery", structure_blueprint)

        # Native concepts in lists should also generate imports
        assert "from pipelex.core.stuffs.image_content import ImageContent" in generated_code
        assert "images: List[ImageContent] = Field(...," in generated_code
        assert issubclass(generated_class, StructuredContent)

    def test_concept_ref_with_module_path_generates_import(self):
        """Test that concept_ref with module_path generates proper import statements."""
        structure_blueprint = {
            "skill": ConceptStructureBlueprint(
                description="The skill being evaluated",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="cv_tech_screening.Skill",
                required=True,
            ),
            "matches_requirement": ConceptStructureBlueprint(
                description="Whether this skill matches a job requirement",
                type=ConceptStructureBlueprintFieldType.BOOLEAN,
                required=True,
            ),
        }

        concept_ref_to_class_info = {
            "cv_tech_screening.Skill": ConceptClassInfo(
                class_name="Skill",
                module_path="pipeline_01.structures.cv_tech_screening__skill",
            ),
        }

        generator = StructureGenerator(concept_ref_to_class_info=concept_ref_to_class_info)
        class_code = generator._generate_class_source_code_from_blueprint("SkillMatchResult", structure_blueprint)  # noqa: SLF001  # pyright: ignore[reportPrivateUsage]

        expected_class_code = '''\
class SkillMatchResult(StructuredContent):
    """Generated SkillMatchResult class"""

    skill: Skill = Field(..., description="The skill being evaluated")
    matches_requirement: bool = Field(..., description="Whether this skill matches a job requirement")'''

        expected_imports = {
            "from enum import Enum",
            "from pipelex.core.stuffs.structured_content import StructuredContent",
            "from pipeline_01.structures.cv_tech_screening__skill import Skill",
            "from pydantic import Field",
            "from typing import Optional, List, Dict, Any, Literal",
        }

        assert class_code == expected_class_code
        assert generator.imports == expected_imports

    def test_list_of_concepts_with_module_path_generates_import(self):
        """Test that list of concepts with module_path generates proper import statements."""
        structure_blueprint = {
            "skill_matches": ConceptStructureBlueprint(
                description="List of skill match results",
                type=ConceptStructureBlueprintFieldType.LIST,
                item_type="concept",
                item_concept_ref="cv_tech_screening.SkillMatchResult",
                required=True,
            ),
            "overall_score": ConceptStructureBlueprint(
                description="Overall match score",
                type=ConceptStructureBlueprintFieldType.INTEGER,
                required=True,
            ),
        }

        concept_ref_to_class_info = {
            "cv_tech_screening.SkillMatchResult": ConceptClassInfo(
                class_name="SkillMatchResult",
                module_path="pipeline_01.structures.cv_tech_screening__skill_match_result",
            ),
        }

        generator = StructureGenerator(concept_ref_to_class_info=concept_ref_to_class_info)
        class_code = generator._generate_class_source_code_from_blueprint("TechAnalysis", structure_blueprint)  # noqa: SLF001  # pyright: ignore[reportPrivateUsage]

        expected_class_code = '''\
class TechAnalysis(StructuredContent):
    """Generated TechAnalysis class"""

    skill_matches: List[SkillMatchResult] = Field(..., description="List of skill match results")
    overall_score: int = Field(..., description="Overall match score")'''

        expected_imports = {
            "from enum import Enum",
            "from pipelex.core.stuffs.structured_content import StructuredContent",
            "from pipeline_01.structures.cv_tech_screening__skill_match_result import SkillMatchResult",
            "from pydantic import Field",
            "from typing import Optional, List, Dict, Any, Literal",
        }

        assert class_code == expected_class_code
        assert generator.imports == expected_imports

    def test_class_info_without_module_path_uses_forward_reference(self):
        """Test that ConceptClassInfo without module_path uses forward reference."""
        structure_blueprint = {
            "item": ConceptStructureBlueprint(
                description="An item reference",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="myapp.SomeItem",
                required=True,
            ),
        }

        concept_ref_to_class_info = {
            "myapp.SomeItem": ConceptClassInfo(
                class_name="SomeItem",
                module_path=None,
            ),
        }

        generator = StructureGenerator(concept_ref_to_class_info=concept_ref_to_class_info)
        generated_code, generated_class = generator.generate_from_structure_blueprint("Container", structure_blueprint)

        expected_code = (
            AUTOGEN_HEADER
            + STANDARD_IMPORTS
            + '''

class Container(StructuredContent):
    """Generated Container class"""

    item: "SomeItem" = Field(..., description="An item reference")
'''
        )
        assert generated_code == expected_code
        assert issubclass(generated_class, StructuredContent)

    def test_multiple_concept_refs_with_same_class_name(self):
        """Test generation with multiple concept refs that have the same structure class name."""
        structure_blueprint = {
            "billing_customer": ConceptStructureBlueprint(
                description="The billing customer",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="billing.Customer",
                required=True,
            ),
            "shipping_customer": ConceptStructureBlueprint(
                description="The shipping customer",
                type=ConceptStructureBlueprintFieldType.CONCEPT,
                concept_ref="shipping.Customer",
                required=False,
            ),
        }

        concept_ref_to_class_info = {
            "billing.Customer": ConceptClassInfo(
                class_name="BillingCustomer",
                module_path="billing.structures.customer",
            ),
            "shipping.Customer": ConceptClassInfo(
                class_name="ShippingCustomer",
                module_path="shipping.structures.customer",
            ),
        }

        generator = StructureGenerator(concept_ref_to_class_info=concept_ref_to_class_info)
        class_code = generator._generate_class_source_code_from_blueprint("Order", structure_blueprint)  # noqa: SLF001  # pyright: ignore[reportPrivateUsage]

        expected_class_code = '''\
class Order(StructuredContent):
    """Generated Order class"""

    billing_customer: BillingCustomer = Field(..., description="The billing customer")
    shipping_customer: Optional[ShippingCustomer] = Field(default=None, description="The shipping customer")'''

        expected_imports = {
            "from enum import Enum",
            "from pipelex.core.stuffs.structured_content import StructuredContent",
            "from billing.structures.customer import BillingCustomer",
            "from shipping.structures.customer import ShippingCustomer",
            "from pydantic import Field",
            "from typing import Optional, List, Dict, Any, Literal",
        }

        assert class_code == expected_class_code
        assert generator.imports == expected_imports
