package tech.easyflow.skill.gitimport;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;

/**
 * {@link GitRepositoryAccessPolicy} 地址安全契约测试。
 */
public class GitRepositoryAccessPolicyTest {

    private GitRepositoryAccessPolicy policy;

    /**
     * 初始化不依赖公网 DNS 的可信测试主机。
     */
    @Before
    public void setUp() {
        SkillGitImportProperties properties = new SkillGitImportProperties();
        properties.setTrustedPrivateHosts(Set.of("git.example.test"));
        policy = new GitRepositoryAccessPolicy(properties);
    }

    /**
     * Spring 容器应选择生产构造器完成组件实例化。
     */
    @Test
    public void springContextUsesProductionConstructor() {
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.registerBean(SkillGitImportProperties.class, SkillGitImportProperties::new);
            context.register(GitRepositoryAccessPolicy.class);
            context.refresh();

            assertNotNull(context.getBean(GitRepositoryAccessPolicy.class));
        }
    }

    /**
     * 仓库地址允许省略 .git 后缀并移除结尾斜线。
     */
    @Test
    public void acceptsHttpsRepositoryWithOptionalGitSuffix() {
        assertEquals(policy.normalizeRepositoryUrl(
                        " https://git.example.test/team/skills/ "),
                "https://git.example.test/team/skills");
        assertEquals(policy.normalizeRepositoryUrl(
                        "https://git.example.test/team/skills.git"),
                "https://git.example.test/team/skills.git");
        assertEquals(policy.normalizeRepositoryUrl("https://192.0.1.1/team/skills.git"),
                "https://192.0.1.1/team/skills.git");
    }

    /**
     * 拒绝非 HTTPS、内嵌凭证和 URL 参数。
     */
    @Test
    public void rejectsUnsafeRepositoryInputs() {
        assertRejected("http://git.example.test/team/skills.git");
        assertRejected("https://user:secret@git.example.test/team/skills.git");
        assertRejected("https://git.example.test/team/skills.git?token=secret");
        assertRejected("https://git.example.test:8443/team/skills.git");
        assertRejected("https://127.0.0.1/team/skills.git");
    }

    /**
     * 实际连接使用策略校验后返回的固定 DNS 地址。
     *
     * @throws Exception 构造测试地址失败
     */
    @Test
    public void resolvesAddressesForPinnedConnection() throws Exception {
        SkillGitImportProperties properties = new SkillGitImportProperties();
        InetAddress publicAddress = InetAddress.getByAddress(new byte[]{93, (byte) 184, (byte) 216, 34});
        GitRepositoryAccessPolicy pinnedPolicy = new GitRepositoryAccessPolicy(
                properties, host -> new InetAddress[]{publicAddress});

        GitRepositoryAccessPolicy.ResolvedConnection resolved = pinnedPolicy.resolveConnection(
                new URL("https://git.example.test/team/skills.git/info/refs"));

        assertEquals("git.example.test", resolved.host());
        assertEquals(publicAddress, resolved.addresses()[0]);
    }

    /**
     * 输入校验后主机重新绑定到内网时，实际连接阶段必须拒绝。
     *
     * @throws Exception 构造测试地址失败
     */
    @Test
    public void rejectsDnsRebindingBeforeConnection() throws Exception {
        SkillGitImportProperties properties = new SkillGitImportProperties();
        InetAddress publicAddress = InetAddress.getByAddress(new byte[]{93, (byte) 184, (byte) 216, 34});
        InetAddress loopback = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
        AtomicInteger resolutions = new AtomicInteger();
        GitRepositoryAccessPolicy rebindingPolicy = new GitRepositoryAccessPolicy(properties,
                host -> resolutions.getAndIncrement() == 0
                        ? new InetAddress[]{publicAddress} : new InetAddress[]{loopback});

        assertEquals("https://git.example.test/team/skills.git",
                rebindingPolicy.normalizeRepositoryUrl("https://git.example.test/team/skills.git"));
        assertThrows(IOException.class, () -> rebindingPolicy.resolveConnection(
                new URL("https://git.example.test/team/skills.git/info/refs")));
    }

    private void assertRejected(String value) {
        assertThrows(BusinessException.class, () -> policy.normalizeRepositoryUrl(value));
    }
}
