package click.kamil.springstatemachineexporter.analysis.spring;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;

class SpringDependencyResolverTest {

    private SpringBeanRegistry registry;
    private SpringDependencyResolver resolver;

    @BeforeEach
    void setUp() {
        registry = new SpringBeanRegistry();
        resolver = new SpringDependencyResolver(registry);
    }

    @Test
    void shouldResolveSingleMatchByType() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.PaymentServiceImpl")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "paymentService");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.PaymentServiceImpl");
    }

    @Test
    void shouldResolveByPrimaryWhenMultipleImplementationsExist() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.NormalService")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .isPrimary(false)
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.PrimaryService")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .isPrimary(true)
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "paymentService");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.PrimaryService");
    }

    @Test
    void shouldResolveByQualifierWhenMultipleImplementationsExist() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceA")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .qualifiers(Set.of("serviceA"))
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceB")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .qualifiers(Set.of("serviceB"))
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", "serviceB", "paymentService");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceB");
    }

    @Test
    void shouldResolveByInjectionNameFallback() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceA")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .beanNames(Set.of("serviceA"))
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceB")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .beanNames(Set.of("specialService"))
                .build());

        // Qualifier is null, Primary is false. Should match by variable name "specialService"
        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "specialService");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceB");
    }

    @Test
    void shouldResolveByOrderWhenOtherFiltersTie() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.LowestOrderService")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(Integer.MAX_VALUE) // Lowest precedence
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.HighestOrderService")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(Integer.MIN_VALUE) // Highest precedence
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.MiddleOrderService")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(0)
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.HighestOrderService");
    }

    @Test
    void shouldResolveByOrderWithCustomValues() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceA")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(400) 
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceB")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(100) 
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceC")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(2147483647 + 400) // This wraps around in Java due to overflow, but let's test a very large valid value
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceC");
    }

    @Test
    void primaryShouldOverrideOrder() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.PrimaryButLowestOrder")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .isPrimary(true)
                .order(Integer.MAX_VALUE)
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.NotPrimaryButHighestOrder")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .isPrimary(false)
                .order(Integer.MIN_VALUE)
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.PrimaryButLowestOrder");
    }

    @Test
    void qualifierShouldOverridePrimaryAndOrder() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.PrimaryService")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .isPrimary(true)
                .order(Integer.MIN_VALUE)
                .qualifiers(Set.of("primary"))
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.QualifiedService")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .isPrimary(false)
                .order(Integer.MAX_VALUE)
                .qualifiers(Set.of("targetQualifier"))
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", "targetQualifier", "someUnknownName");

        assertThat(resolved).hasSize(1);
        assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.QualifiedService");
    }

    @Test
    void shouldReturnMultipleWhenOrderIsTied() {
        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceA")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(10)
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceB")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(10)
                .build());

        registry.addBean(SpringBean.builder()
                .typeFqn("com.example.ServiceC")
                .assignableTypes(Set.of("com.example.PaymentService"))
                .order(20)
                .build());

        List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");

        assertThat(resolved).hasSize(2);
        assertThat(resolved.stream().map(SpringBean::getTypeFqn))
                .containsExactlyInAnyOrder("com.example.ServiceA", "com.example.ServiceB");
    }
}
