package com.magistr.app.config.tenant;

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

import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class TenantRoutingDataSourceTest {

    @AfterEach
    void clearTenantContext() {
        TenantContext.clear();
    }

    @Test
    void atomicallyPublishesOneImmutableStateAndKeepsObtainedConnectionUsable() throws Exception {
        TenantRoutingDataSource routing = new TenantRoutingDataSource();
        DataSource oldDataSource = mock(DataSource.class);
        DataSource newDataSource = mock(DataSource.class);
        Connection oldConnection = mock(Connection.class);
        Connection newConnection = mock(Connection.class);
        when(oldDataSource.getConnection()).thenReturn(oldConnection);
        when(newDataSource.getConnection()).thenReturn(newConnection);
        when(oldConnection.isValid(1)).thenReturn(true);
        TenantConfig oldConfig = config("Старый", "ALPHA", "jdbc:old");
        TenantConfig newConfig = config("Новый", "alpha", "jdbc:new");

        assertThat(routing.swapTenant(oldConfig, oldDataSource)).isNull();
        oldConfig.setUrl("jdbc:mutated-request");
        TenantContext.setCurrentTenant("alpha");
        Connection inFlight = routing.getConnection();
        assertThat(inFlight).isSameAs(oldConnection);

        TenantRoutingDataSource.TenantState previous = routing.swapTenant(newConfig, newDataSource);

        assertThat(previous).isNotNull();
        assertThat(previous.dataSource()).isSameAs(oldDataSource);
        assertThat(previous.config().getUrl()).isEqualTo("jdbc:old");
        assertThat(routing.getConnection()).isSameAs(newConnection);
        assertThat(inFlight.isValid(1)).isTrue();
        verify(oldConnection).isValid(1);
        assertThat(routing.snapshotTenantConfigs().get("alpha").getUrl()).isEqualTo("jdbc:new");
    }

    @Test
    void snapshotsAreDetachedAndUnmodifiableAndRemovalReturnsRetiredState() {
        TenantRoutingDataSource routing = new TenantRoutingDataSource();
        DataSource dataSource = mock(DataSource.class);
        TenantConfig config = config("Alpha", "alpha", "jdbc:alpha");
        routing.swapTenant(config, dataSource);

        Map<String, TenantConfig> snapshot = routing.snapshotTenantConfigs();
        snapshot.get("alpha").setUrl("jdbc:changed-copy");

        assertThat(routing.snapshotTenantConfigs().get("alpha").getUrl()).isEqualTo("jdbc:alpha");
        assertThatThrownBy(() -> snapshot.put("beta", config("Beta", "beta", "jdbc:beta")))
                .isInstanceOf(UnsupportedOperationException.class);
        TenantRoutingDataSource.TenantState removed = routing.removeTenantAtomically(" ALPHA ");
        assertThat(removed).isNotNull();
        assertThat(removed.dataSource()).isSameAs(dataSource);
        assertThat(routing.snapshotTenant("alpha")).isEmpty();
        assertThat(routing.getTenantDataSource("alpha")).isEmpty();
    }

    private TenantConfig config(String name, String domain, String url) {
        return new TenantConfig(name, domain, url, "user", "password");
    }
}
