package net.siegeln.cameleer.saas.license;

import net.siegeln.cameleer.saas.audit.AuditAction;
import net.siegeln.cameleer.saas.audit.AuditService;
import net.siegeln.cameleer.saas.tenant.Tier;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.TenantStatus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.Duration;
import java.util.Optional;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class LicenseServiceTest {

    @Mock
    private LicenseRepository licenseRepository;

    @Mock
    private AuditService auditService;

    private LicenseService licenseService;

    @BeforeEach
    void setUp() {
        licenseService = new LicenseService(licenseRepository, auditService);
    }

    private TenantEntity createTenant(Tier tier) {
        var tenant = new TenantEntity();
        tenant.setName("Test Tenant");
        tenant.setSlug("test");
        tenant.setTier(tier);
        tenant.setStatus(TenantStatus.ACTIVE);
        try {
            var idField = TenantEntity.class.getDeclaredField("id");
            idField.setAccessible(true);
            idField.set(tenant, UUID.randomUUID());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return tenant;
    }

    private static LicenseEntity withGeneratedId(LicenseEntity entity) {
        try {
            var idField = LicenseEntity.class.getDeclaredField("id");
            idField.setAccessible(true);
            idField.set(entity, UUID.randomUUID());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return entity;
    }

    @Test
    void generateLicense_producesUuidToken() {
        var tenant = createTenant(Tier.MID);
        when(licenseRepository.save(any(LicenseEntity.class))).thenAnswer(inv -> withGeneratedId(inv.getArgument(0)));

        var license = licenseService.generateLicense(tenant, Duration.ofDays(365), UUID.randomUUID());

        assertThat(license.getToken()).isNotBlank();
        // Token must be a valid UUID string
        assertThat(UUID.fromString(license.getToken())).isNotNull();
        assertThat(license.getTier()).isEqualTo("MID");
    }

    @Test
    void generateLicense_setsCorrectFeaturesForTier() {
        var tenant = createTenant(Tier.HIGH);
        when(licenseRepository.save(any(LicenseEntity.class))).thenAnswer(inv -> withGeneratedId(inv.getArgument(0)));

        var license = licenseService.generateLicense(tenant, Duration.ofDays(30), UUID.randomUUID());

        assertThat(license.getFeatures()).containsEntry("debugger", true);
        assertThat(license.getFeatures()).containsEntry("replay", true);
    }

    @Test
    void generateLicense_setsCorrectLimitsForTier() {
        var tenant = createTenant(Tier.LOW);
        when(licenseRepository.save(any(LicenseEntity.class))).thenAnswer(inv -> withGeneratedId(inv.getArgument(0)));

        var license = licenseService.generateLicense(tenant, Duration.ofDays(30), UUID.randomUUID());

        assertThat(license.getLimits()).containsEntry("max_agents", 3);
        assertThat(license.getLimits()).containsEntry("retention_days", 7);
    }

    @Test
    void verifyLicenseToken_validTokenReturnsPayload() {
        var tenant = createTenant(Tier.MID);
        when(licenseRepository.save(any(LicenseEntity.class))).thenAnswer(inv -> withGeneratedId(inv.getArgument(0)));

        var license = licenseService.generateLicense(tenant, Duration.ofDays(30), UUID.randomUUID());

        when(licenseRepository.findByToken(license.getToken())).thenReturn(Optional.of(license));

        var payload = licenseService.verifyLicenseToken(license.getToken());

        assertThat(payload).isPresent();
        assertThat(payload.get().get("tier")).isEqualTo("MID");
        assertThat(payload.get().get("tenant_id")).isEqualTo(tenant.getId().toString());
    }

    @Test
    void verifyLicenseToken_unknownTokenReturnsEmpty() {
        when(licenseRepository.findByToken(any())).thenReturn(Optional.empty());

        var payload = licenseService.verifyLicenseToken("unknown-token");

        assertThat(payload).isEmpty();
    }

    @Test
    void generateLicense_logsAuditEvent() {
        var tenant = createTenant(Tier.LOW);
        var actorId = UUID.randomUUID();
        when(licenseRepository.save(any(LicenseEntity.class))).thenAnswer(inv -> withGeneratedId(inv.getArgument(0)));

        licenseService.generateLicense(tenant, Duration.ofDays(30), actorId);

        var actionCaptor = ArgumentCaptor.forClass(AuditAction.class);
        verify(auditService).log(any(), any(), any(), actionCaptor.capture(), any(), any(), any(), any(), any());
        assertThat(actionCaptor.getValue()).isEqualTo(AuditAction.LICENSE_GENERATE);
    }
}
