package com.malk.benteler.service;

import com.malk.benteler.dto.EiamBatchResult;
import com.malk.benteler.dto.EiamBatchStage;
import com.malk.benteler.dto.EiamCreateUserItem;
import com.malk.benteler.dto.EiamDeleteUserItem;
import com.malk.benteler.dto.EiamUpdateUserItem;
import com.malk.server.common.McException;
import com.malk.server.eiam.EiamApiResponse;
import com.malk.server.eiam.EiamConf;
import com.malk.server.eiam.EiamCustomField;
import com.malk.service.eiam.EiamClient_User;
import com.malk.service.eiam.EiamService;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * {@link EiamLocalService} 批量编排单元测试。
 */
public class EiamLocalServiceTest {

    private EiamService eiamService;
    private EiamClient_User eiamClientUser;
    private EiamLocalService localService;

    @Before
    public void setUp() {
        eiamService = mock(EiamService.class);
        eiamClientUser = mock(EiamClient_User.class);
        EiamConf eiamConf = new EiamConf();
        eiamConf.setInstanceId("idaas_test");
        eiamConf.setApplicationId("app_test");
        localService = new EiamLocalService(eiamService, eiamClientUser, eiamConf);
        when(eiamService.getAccessToken()).thenReturn("token_test");
    }

    @Test
    public void batchCreate_allSuccess_returnsOrderedDetails() {
        when(eiamClientUser.createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), anyMap()))
                .thenReturn(successResponse("user_1"), successResponse("user_2"));

        EiamBatchResult result = localService.batchCreate(Arrays.asList(
                createItem("form_1", "13800000001", "张一"),
                createItem("form_2", "13800000002", "张二")));

        assertEquals(2, result.getTotal());
        assertEquals(2, result.getSuccessCount());
        assertEquals(0, result.getFailedCount());
        assertEquals("form_1", result.getItems().get(0).getFormInstanceId());
        assertEquals("user_2", result.getItems().get(1).getUserId());
        verify(eiamService, times(1)).getAccessToken();
    }

    @Test
    public void batchCreate_partialFailure_continuesFollowingItems() {
        when(eiamClientUser.createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), anyMap()))
                .thenReturn(successResponse("user_1"),
                        errorResponse("ResourceDuplicated.Username", "username already exists"),
                        successResponse("user_3"));

        EiamBatchResult result = localService.batchCreate(Arrays.asList(
                createItem("form_1", "13800000001", "张一"),
                createItem("form_2", "13800000002", "张二"),
                createItem("form_3", "13800000003", "张三")));

        assertEquals(2, result.getSuccessCount());
        assertEquals(1, result.getFailedCount());
        assertFalse(result.getItems().get(1).isSuccess());
        assertEquals("ResourceDuplicated.Username", result.getItems().get(1).getCode());
        assertTrue(result.getItems().get(2).isSuccess());
        verify(eiamClientUser, times(3)).createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), anyMap());
    }

    @Test
    public void batchCreate_invalidItem_isolatedFromFollowingItems() {
        EiamCreateUserItem invalid = createItem("form_1", null, "无账户名");
        when(eiamClientUser.createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), anyMap()))
                .thenReturn(successResponse("user_2"));

        EiamBatchResult result = localService.batchCreate(Arrays.asList(
                invalid, createItem("form_2", "13800000002", "张二")));

        assertEquals(1, result.getSuccessCount());
        assertEquals(1, result.getFailedCount());
        assertEquals(EiamBatchStage.VALIDATE, result.getItems().get(0).getStage());
        assertTrue(result.getItems().get(1).isSuccess());
        verify(eiamClientUser, times(1)).createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), anyMap());
    }

    @Test
    public void batchCreate_missingCompanyCode_returnsValidationFailure() {
        EiamCreateUserItem item = createItem("form_1", "13800000001", "缺少公司代码");
        item.setCustomFields(Arrays.asList(customField("department", "Technical accounts"),
                customField("company_name", "测试公司"),
                customField("job_title", "Software Engineer")));

        EiamBatchResult result = localService.batchCreate(Collections.singletonList(item));

        assertEquals(0, result.getSuccessCount());
        assertEquals(1, result.getFailedCount());
        assertEquals(EiamBatchStage.VALIDATE, result.getItems().get(0).getStage());
        assertTrue(result.getItems().get(0).getMessage().contains("公司代码"));
        verify(eiamClientUser, times(0)).createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), anyMap());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void batchCreate_passwordConfigOmitsNotificationChannels() {
        when(eiamClientUser.createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), anyMap()))
                .thenReturn(successResponse("user_1"));

        localService.batchCreate(Collections.singletonList(
                createItem("form_1", "13800000001", "测试人员")));

        ArgumentCaptor<Map> bodyCaptor = ArgumentCaptor.forClass(Map.class);
        verify(eiamClientUser).createUser(anyString(), anyString(), anyString(),
                anyString(), anyString(), bodyCaptor.capture());
        Map<String, Object> passwordConfig = (Map<String, Object>) bodyCaptor.getValue()
                .get("passwordInitializationConfig");
        assertEquals("custom", passwordConfig.get("passwordInitializationPolicyPriority"));
        assertEquals("enabled", passwordConfig.get("passwordForcedUpdateStatus"));
        assertEquals("random", passwordConfig.get("passwordInitializationType"));
        assertFalse(passwordConfig.containsKey("userNotificationChannels"));
    }

    @Test
    public void batchUpdate_primaryOrgFailure_reportsPartialUpdate() {
        EiamUpdateUserItem item = new EiamUpdateUserItem();
        item.setFormInstanceId("form_update");
        item.setUserId("user_1");
        item.setDisplayName("新姓名");
        item.setPrimaryOrganizationalUnitId("ou_new");
        when(eiamClientUser.patchUser(anyString(), anyString(), anyString(),
                anyString(), anyMap())).thenReturn(new EiamApiResponse());
        when(eiamClientUser.setUserPrimaryOrganizationalUnit(anyString(), anyString(),
                anyString(), anyString(), anyString()))
                .thenReturn(errorResponse("OrganizationUnitIdNotInScopes", "OU not in scope"));

        EiamBatchResult result = localService.batchUpdate(Collections.singletonList(item));

        assertEquals(0, result.getSuccessCount());
        assertEquals(1, result.getFailedCount());
        assertEquals(EiamBatchStage.SET_PRIMARY_ORG, result.getItems().get(0).getStage());
        assertTrue(result.getItems().get(0).getMessage().contains("账户属性可能已更新"));
    }

    @Test
    public void batchUpdate_primaryOrgOnly_callsOnlyPrimaryOrgAction() {
        EiamUpdateUserItem item = updateItem();
        item.setPrimaryOrganizationalUnitId("ou_new");
        when(eiamClientUser.setUserPrimaryOrganizationalUnit(anyString(), anyString(),
                anyString(), anyString(), anyString())).thenReturn(new EiamApiResponse());

        EiamBatchResult result = localService.batchUpdate(Collections.singletonList(item));

        assertEquals(1, result.getSuccessCount());
        verify(eiamClientUser).setUserPrimaryOrganizationalUnit(anyString(), anyString(),
                anyString(), anyString(), anyString());
        verify(eiamClientUser, never()).patchUser(anyString(), anyString(), anyString(),
                anyString(), anyMap());
        verify(eiamClientUser, never()).addUserToOrganizationalUnits(anyString(), anyString(),
                anyString(), anyString(), anyList());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void batchUpdate_phone_updatesUsernameAndPhoneDefaults() {
        EiamUpdateUserItem item = updateItem();
        item.setUpdateContents(Collections.singletonList(BentelerYidaFormMapper.UPDATE_PHONE));
        item.setUsername("17612168216");
        item.setPhoneNumber("17612168216");
        when(eiamClientUser.patchUser(anyString(), anyString(), anyString(),
                anyString(), anyMap())).thenReturn(new EiamApiResponse());

        EiamBatchResult result = localService.batchUpdate(Collections.singletonList(item));

        assertEquals(1, result.getSuccessCount());
        ArgumentCaptor<Map> bodyCaptor = ArgumentCaptor.forClass(Map.class);
        verify(eiamClientUser).patchUser(anyString(), anyString(), anyString(),
                anyString(), bodyCaptor.capture());
        assertEquals("17612168216", bodyCaptor.getValue().get("username"));
        assertEquals("17612168216", bodyCaptor.getValue().get("phoneNumber"));
        assertEquals("86", bodyCaptor.getValue().get("phoneRegion"));
        assertEquals(true, bodyCaptor.getValue().get("phoneNumberVerified"));
    }

    @Test
    public void batchUpdate_additionalOrgOnly_preservesPrimaryOrg() {
        EiamUpdateUserItem item = updateItem();
        item.setAdditionalOrganizationalUnitId("ou_additional");
        when(eiamClientUser.addUserToOrganizationalUnits(anyString(), anyString(), anyString(),
                anyString(), anyList())).thenReturn(new EiamApiResponse());

        EiamBatchResult result = localService.batchUpdate(Collections.singletonList(item));

        assertEquals(1, result.getSuccessCount());
        verify(eiamClientUser).addUserToOrganizationalUnits(anyString(), anyString(), anyString(),
                anyString(), anyList());
        verify(eiamClientUser, never()).setUserPrimaryOrganizationalUnit(anyString(), anyString(),
                anyString(), anyString(), anyString());
    }

    @Test
    public void batchUpdate_additionalOrgFailure_reportsEarlierPatch() {
        EiamUpdateUserItem item = updateItem();
        item.setUsername("17612168216");
        item.setPhoneNumber("17612168216");
        item.setAdditionalOrganizationalUnitId("ou_additional");
        when(eiamClientUser.patchUser(anyString(), anyString(), anyString(),
                anyString(), anyMap())).thenReturn(new EiamApiResponse());
        when(eiamClientUser.addUserToOrganizationalUnits(anyString(), anyString(), anyString(),
                anyString(), anyList())).thenReturn(
                errorResponse("OrganizationUnitIdNotInScopes", "OU not in scope"));

        EiamBatchResult result = localService.batchUpdate(Collections.singletonList(item));

        assertEquals(EiamBatchStage.ADD_ORG, result.getItems().get(0).getStage());
        assertTrue(result.getItems().get(0).getMessage().contains("账户属性可能已更新"));
    }

    @Test
    public void batchDelete_partialFailure_continuesFollowingItems() {
        when(eiamClientUser.deleteUser(anyString(), anyString(), anyString(), anyString()))
                .thenReturn(errorResponse("ResourceNotFound.User", "user not found"),
                        new EiamApiResponse());

        EiamBatchResult result = localService.batchDelete(Arrays.asList(
                deleteItem("form_1", "missing_user"), deleteItem("form_2", "user_2")));

        assertEquals(1, result.getSuccessCount());
        assertEquals(1, result.getFailedCount());
        assertEquals(EiamBatchStage.DELETE_USER, result.getItems().get(0).getStage());
        assertTrue(result.getItems().get(1).isSuccess());
        verify(eiamClientUser, times(2)).deleteUser(anyString(), anyString(),
                anyString(), anyString());
    }

    @Test(expected = McException.class)
    public void emptyBatch_throwsRequestLevelError() {
        localService.batchCreate(Collections.emptyList());
    }

    private EiamCreateUserItem createItem(String formInstanceId, String username,
                                          String displayName) {
        EiamCreateUserItem item = new EiamCreateUserItem();
        item.setFormInstanceId(formInstanceId);
        item.setUsername(username);
        item.setDisplayName(displayName);
        item.setPrimaryOrganizationalUnitId("ou_root");
        item.setCustomFields(Arrays.asList(customField("department", "Technical accounts"),
                customField("company_name", "测试公司"),
                customField("company_code", "COMPANY-01"),
                customField("job_title", "Software Engineer")));
        return item;
    }

    private EiamCustomField customField(String fieldName, String fieldValue) {
        EiamCustomField field = new EiamCustomField();
        field.setFieldName(fieldName);
        field.setFieldValue(fieldValue);
        return field;
    }

    private EiamDeleteUserItem deleteItem(String formInstanceId, String userId) {
        EiamDeleteUserItem item = new EiamDeleteUserItem();
        item.setFormInstanceId(formInstanceId);
        item.setUserId(userId);
        return item;
    }

    private EiamUpdateUserItem updateItem() {
        EiamUpdateUserItem item = new EiamUpdateUserItem();
        item.setFormInstanceId("form_update");
        item.setUserId("user_1");
        return item;
    }

    private EiamApiResponse successResponse(String userId) {
        EiamApiResponse response = new EiamApiResponse();
        response.setUserId(userId);
        return response;
    }

    private EiamApiResponse errorResponse(String code, String message) {
        EiamApiResponse response = new EiamApiResponse();
        response.setCode(code);
        response.setMessage(message);
        return response;
    }
}
