package tech.easyflow.job.service;

import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import tech.easyflow.ai.service.WorkflowUsageAuthorizationService;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.constant.enums.EnumJobStatus;
import tech.easyflow.common.constant.enums.EnumJobType;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.job.entity.SysJob;
import tech.easyflow.job.job.JobConstant;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;

import java.math.BigInteger;
import java.util.Map;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * {@link WorkflowJobExecutionService} 运行时授权测试。
 */
public class WorkflowJobExecutionServiceTest {

    /**
     * 验证每次触发都会按数据库当前状态授权，并将任务创建账号注入工作流参数。
     */
    @Test
    public void shouldReauthorizeAndRestoreServerControlledOwner() {
        BigInteger jobId = BigInteger.valueOf(101);
        BigInteger tenantId = BigInteger.valueOf(201);
        BigInteger accountId = BigInteger.valueOf(301);
        BigInteger workflowId = BigInteger.valueOf(401);
        SysJobService jobService = mock(SysJobService.class);
        SysAccountService accountService = mock(SysAccountService.class);
        WorkflowUsageAuthorizationService authorizationService =
                mock(WorkflowUsageAuthorizationService.class);
        ChainExecutor chainExecutor = mock(ChainExecutor.class);
        SysJob currentJob = workflowJob(jobId, tenantId, accountId, workflowId);
        SysAccount account = account(accountId, tenantId, EnumDataStatus.AVAILABLE.getCode());
        when(jobService.getById(jobId)).thenReturn(currentJob);
        when(accountService.getById(accountId)).thenReturn(account);
        Map<String, Object> executionResult = Map.of("status", "done");
        when(chainExecutor.execute(eq(workflowId.toString()), anyMap()))
                .thenReturn(executionResult);
        WorkflowJobExecutionService service = new WorkflowJobExecutionService(
                jobService,
                accountService,
                authorizationService,
                chainExecutor);

        Object result = service.execute(scheduledJob(jobId, tenantId));

        Assert.assertSame(executionResult, result);
        ArgumentCaptor<LoginAccount> accountCaptor =
                ArgumentCaptor.forClass(LoginAccount.class);
        verify(authorizationService).requireUsableWorkflow(
                eq(workflowId),
                accountCaptor.capture(),
                anyString());
        Assert.assertEquals(accountCaptor.getValue().getId(), accountId);
        Assert.assertEquals(accountCaptor.getValue().getTenantId(), tenantId);

        @SuppressWarnings("unchecked")
        ArgumentCaptor<Map<String, Object>> paramsCaptor =
                ArgumentCaptor.forClass(Map.class);
        verify(chainExecutor).execute(eq(workflowId.toString()), paramsCaptor.capture());
        Object loginUser = paramsCaptor.getValue().get(Constants.LOGIN_USER_KEY);
        Assert.assertTrue(loginUser instanceof LoginAccount);
        Assert.assertEquals(((LoginAccount) loginUser).getId(), accountId);
    }

    /**
     * 验证权限已撤销时执行器不会启动工作流。
     */
    @Test
    public void shouldRejectExecutionAfterPermissionRevoked() {
        BigInteger jobId = BigInteger.valueOf(102);
        BigInteger tenantId = BigInteger.valueOf(202);
        BigInteger accountId = BigInteger.valueOf(302);
        BigInteger workflowId = BigInteger.valueOf(402);
        SysJobService jobService = mock(SysJobService.class);
        SysAccountService accountService = mock(SysAccountService.class);
        WorkflowUsageAuthorizationService authorizationService =
                mock(WorkflowUsageAuthorizationService.class);
        ChainExecutor chainExecutor = mock(ChainExecutor.class);
        when(jobService.getById(jobId))
                .thenReturn(workflowJob(jobId, tenantId, accountId, workflowId));
        when(accountService.getById(accountId))
                .thenReturn(account(accountId, tenantId, EnumDataStatus.AVAILABLE.getCode()));
        when(authorizationService.requireUsableWorkflow(
                eq(workflowId),
                any(LoginAccount.class),
                anyString()))
                .thenThrow(new BusinessException("工作流权限已撤销"));
        WorkflowJobExecutionService service = new WorkflowJobExecutionService(
                jobService,
                accountService,
                authorizationService,
                chainExecutor);

        BusinessException exception = Assert.assertThrows(
                BusinessException.class,
                () -> service.execute(scheduledJob(jobId, tenantId))
        );

        Assert.assertTrue(exception.getMessage().contains("权限已撤销"));
        verify(chainExecutor, never()).execute(anyString(), anyMap());
    }

    /**
     * 验证任务快照与数据库租户不一致时执行失败。
     */
    @Test
    public void shouldRejectCrossTenantJobSnapshot() {
        BigInteger jobId = BigInteger.valueOf(103);
        BigInteger tenantId = BigInteger.valueOf(203);
        SysJobService jobService = mock(SysJobService.class);
        SysAccountService accountService = mock(SysAccountService.class);
        WorkflowUsageAuthorizationService authorizationService =
                mock(WorkflowUsageAuthorizationService.class);
        ChainExecutor chainExecutor = mock(ChainExecutor.class);
        when(jobService.getById(jobId)).thenReturn(workflowJob(
                jobId,
                tenantId,
                BigInteger.valueOf(303),
                BigInteger.valueOf(403)));
        WorkflowJobExecutionService service = new WorkflowJobExecutionService(
                jobService,
                accountService,
                authorizationService,
                chainExecutor);

        IllegalStateException exception = Assert.assertThrows(
                IllegalStateException.class,
                () -> service.execute(scheduledJob(jobId, BigInteger.valueOf(999)))
        );

        Assert.assertTrue(exception.getMessage().contains("租户"));
        verify(authorizationService, never()).requireUsableWorkflow(
                any(BigInteger.class),
                any(LoginAccount.class),
                anyString());
    }

    /**
     * 创建 Quartz 任务快照。
     *
     * @param jobId 定时任务 ID
     * @param tenantId 租户 ID
     * @return 任务快照
     */
    private SysJob scheduledJob(BigInteger jobId, BigInteger tenantId) {
        SysJob job = new SysJob();
        job.setId(jobId);
        job.setTenantId(tenantId);
        job.setJobType(EnumJobType.TINY_FLOW.getCode());
        return job;
    }

    /**
     * 创建数据库中的工作流定时任务。
     *
     * @param jobId 定时任务 ID
     * @param tenantId 租户 ID
     * @param accountId 任务创建账号 ID
     * @param workflowId 工作流 ID
     * @return 工作流定时任务
     */
    private SysJob workflowJob(
            BigInteger jobId,
            BigInteger tenantId,
            BigInteger accountId,
            BigInteger workflowId) {
        SysJob job = new SysJob();
        job.setId(jobId);
        job.setTenantId(tenantId);
        job.setCreatedBy(accountId);
        job.setStatus(EnumJobStatus.RUNNING.getCode());
        job.setJobType(EnumJobType.TINY_FLOW.getCode());
        job.setJobParams(Map.of(
                JobConstant.WORKFLOW_KEY, workflowId.toString(),
                JobConstant.WORKFLOW_PARAMS_KEY, Map.of("question", "hello")
        ));
        return job;
    }

    /**
     * 创建系统账号。
     *
     * @param accountId 账号 ID
     * @param tenantId 租户 ID
     * @param status 账号状态
     * @return 系统账号
     */
    private SysAccount account(
            BigInteger accountId,
            BigInteger tenantId,
            Integer status) {
        SysAccount account = new SysAccount();
        account.setId(accountId);
        account.setTenantId(tenantId);
        account.setStatus(status);
        return account;
    }
}
