package com.gzzm.lobster.api;

import com.gzzm.lobster.artifact.Artifact;
import com.gzzm.lobster.artifact.ArtifactService;
import com.gzzm.lobster.audit.AuditService;
import com.gzzm.lobster.common.ArtifactStatus;
import com.gzzm.lobster.common.ArtifactType;
import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.identity.UserContextHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Date;
import java.util.Map;

import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;

/**
 * ArtifactApi 直写入口最小单元测试。
 */
public class ArtifactApiTest {

    private ArtifactApi api;
    private ArtifactService artifactService;
    private AuditService auditService;

    @Before
    public void setup() throws Exception {
        api = new ArtifactApi();
        artifactService = mock(ArtifactService.class);
        auditService = mock(AuditService.class);
        inject("artifactService", artifactService);
        inject("auditService", auditService);

        UserContextHolder.set(new UserContext("u_unit", "ext", "dept", "org", "张三",
                Collections.<String>emptySet()));
    }

    @After
    public void teardown() {
        UserContextHolder.clear();
    }

    @Test
    public void emptyArtifactIdRejected() throws Exception {
        try {
            api.updateArtifact("", "hello", "");
            fail("should reject empty id");
        } catch (LobsterException e) {
            assertEquals("artifact.bad_id", e.getCode());
        }
    }

    @Test
    public void nullContentRejected() throws Exception {
        try {
            api.updateArtifact("art_x", null, null);
            fail("should reject null content");
        } catch (LobsterException e) {
            assertEquals("artifact.bad_body", e.getCode());
        }
    }

    @Test
    public void oversizedContentRejected() throws Exception {
        // 2MB > 1MB cap
        char[] big = new char[2 * 1024 * 1024];
        java.util.Arrays.fill(big, 'x');
        try {
            api.updateArtifact("art_x", new String(big), null);
            fail("should reject oversize content");
        } catch (LobsterException e) {
            assertEquals("artifact.too_large", e.getCode());
        }
        verifyNoInteractions(artifactService);
    }

    @Test
    public void successfulUpdateReturnsMetadataAndAudits() throws Exception {
        Artifact updated = new Artifact();
        updated.setArtifactId("art_x");
        updated.setTitle("hi");
        updated.setVersion(3);
        updated.setContentSize(42L);
        updated.setUpdateTime(new Date());
        updated.setThreadId("th_1");
        updated.setStatus(ArtifactStatus.active);
        updated.setArtifactType(ArtifactType.GENERATED_DOCUMENT);
        when(artifactService.overwrite(eq("art_x"), any(UserContext.class),
                eq("new content"), eq("new title")))
                .thenReturn(updated);

        Map<String, Object> result = api.updateArtifact("art_x", "new content", "new title");

        assertEquals("art_x", result.get("artifactId"));
        assertEquals(3, result.get("version"));
        assertEquals(42L, result.get("contentSize"));
        // Fix B8: 用 ArgumentMatchers.<String>isNull() 做类型化 null-matcher，
        // 避免 (String) isNull() 在 Mockito 4 混用规则里被识别为原始 null
        verify(auditService).record(
                any(UserContext.class),
                eq("th_1"),
                org.mockito.ArgumentMatchers.<String>isNull(),
                eq("artifact.direct_write"),
                eq("artifact"), eq("art_x"),
                eq("ok"), any());
    }

    private void inject(String field, Object value) throws Exception {
        Field f = ArtifactApi.class.getDeclaredField(field);
        f.setAccessible(true);
        f.set(api, value);
    }
}
