
import pytest
from unittest.mock import MagicMock, patch
from typing import Type, List, Optional
from pydantic import BaseModel, ValidationError

from semantica.semantic_extract.providers import BaseProvider
from semantica.semantic_extract.methods import (
    extract_entities_llm, 
    extract_relations_llm, 
    extract_triplets_llm
)
from semantica.semantic_extract.schemas import EntitiesResponse, RelationsResponse, TripletsResponse
from semantica.semantic_extract.ner_extractor import Entity
from semantica.semantic_extract.relation_extractor import Relation

# Mock Pydantic models for responses
class MockEntity(BaseModel):
    text: str
    label: str
    start: int = 0
    end: int = 0
    confidence: float = 1.0

class MockEntitiesResponse(BaseModel):
    entities: List[MockEntity]

class MockRelation(BaseModel):
    subject: str
    predicate: str
    object: str
    confidence: float = 1.0

class MockRelationsResponse(BaseModel):
    relations: List[MockRelation]

class MockTriplet(BaseModel):
    subject: str
    predicate: str
    object: str
    confidence: float = 1.0

class MockTripletsResponse(BaseModel):
    triplets: List[MockTriplet]

class MockProvider(BaseProvider):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.generate_typed_called = False
        self.generate_structured_called = False
        self.model = "mock-model"
        self.is_available_val = True

    def is_available(self) -> bool:
        return self.is_available_val

    def generate(self, prompt: str, **kwargs) -> str:
        return "{}"

    def generate_structured(self, prompt: str, **kwargs) -> dict:
        self.generate_structured_called = True
        if "entities" in prompt.lower():
            return [{"text": "Apple", "label": "ORG", "start": 0, "end": 5}]
        elif "relations" in prompt.lower():
            return [{"subject": "Steve Jobs", "predicate": "founded", "object": "Apple"}]
        elif "triplets" in prompt.lower():
            return [{"subject": "Steve Jobs", "predicate": "founded", "object": "Apple"}]
        return {}

    def generate_typed(
        self, 
        prompt: str, 
        schema: Type[BaseModel], 
        max_retries: int = 3, 
        **kwargs
    ) -> BaseModel:
        self.generate_typed_called = True
        
        if schema.__name__ == "EntitiesResponse":
            return EntitiesResponse(entities=[
                {"text": "Apple", "label": "ORG", "start_char": 0, "end_char": 5, "confidence": 0.99}
            ])
        elif schema.__name__ == "RelationsResponse":
            return RelationsResponse(relations=[
                {"subject": "Steve Jobs", "predicate": "founded", "object": "Apple", "confidence": 0.95}
            ])
        elif schema.__name__ == "TripletsResponse":
            return TripletsResponse(triplets=[
                {"subject": "Steve Jobs", "predicate": "founded", "object": "Apple", "confidence": 0.95}
            ])
        return schema()

@pytest.fixture
def mock_provider():
    return MockProvider()

@patch("semantica.semantic_extract.methods.create_provider")
def test_extract_entities_typed(mock_create_provider, mock_provider):
    mock_create_provider.return_value = mock_provider
    
    text = "Apple was founded by Steve Jobs."
    entities = extract_entities_llm(
        text, 
        provider="mock", 
        structured_output_mode="typed"
    )
    
    assert mock_provider.generate_typed_called
    assert len(entities) == 1
    assert entities[0].text == "Apple"
    assert entities[0].label == "ORG"
    assert entities[0].metadata["extraction_method"] == "llm_typed"

@patch("semantica.semantic_extract.methods.create_provider")
def test_extract_entities_legacy(mock_create_provider, mock_provider):
    mock_create_provider.return_value = mock_provider
    
    text = "Apple was founded by Steve Jobs."
    entities = extract_entities_llm(
        text, 
        provider="mock", 
        structured_output_mode="legacy"
    )
    
    # Legacy mode now redirects to typed mode
    assert mock_provider.generate_typed_called
    assert not mock_provider.generate_structured_called
    assert len(entities) == 1
    assert entities[0].text == "Apple"
    assert entities[0].label == "ORG"
    assert entities[0].metadata["extraction_method"] == "llm_typed"

@patch("semantica.semantic_extract.methods.create_provider")
def test_extract_relations_typed(mock_create_provider, mock_provider):
    mock_create_provider.return_value = mock_provider
    
    text = "Steve Jobs founded Apple."
    entities = [
        Entity(text="Steve Jobs", label="PERSON", start_char=0, end_char=10),
        Entity(text="Apple", label="ORG", start_char=19, end_char=24)
    ]
    
    relations = extract_relations_llm(
        text,
        entities=entities,
        provider="mock",
        structured_output_mode="typed"
    )
    
    assert mock_provider.generate_typed_called
    assert len(relations) == 1
    assert relations[0].subject.text == "Steve Jobs"
    assert relations[0].object.text == "Apple"
    assert relations[0].predicate == "founded"
    assert relations[0].metadata["extraction_method"] == "llm_typed"

@patch("semantica.semantic_extract.methods.create_provider")
def test_extract_triplets_typed(mock_create_provider, mock_provider):
    mock_create_provider.return_value = mock_provider
    
    text = "Steve Jobs founded Apple."
    
    triplets = extract_triplets_llm(
        text,
        provider="mock",
        structured_output_mode="typed"
    )
    
    assert mock_provider.generate_typed_called
    assert len(triplets) == 1
    assert triplets[0].subject == "Steve Jobs"
    assert triplets[0].object == "Apple"
    assert triplets[0].predicate == "founded"
    assert triplets[0].metadata["extraction_method"] == "llm_typed"

if __name__ == "__main__":
    pytest.main([__file__])
