package tech.easyflow.ai.node;

import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.util.IdUtil;
import com.easyagents.core.util.StringUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.tenant.TenantManager;
import com.easyagents.flow.core.chain.Chain;
import com.easyagents.flow.core.chain.Parameter;
import com.easyagents.flow.core.chain.runtime.RetryableTriggerException;
import com.easyagents.flow.core.node.BaseNode;
import com.easyagents.flow.core.util.IoBulkhead;
import tech.easyflow.ai.entity.Resource;
import tech.easyflow.ai.easyagentsflow.config.WorkflowRuntimeProperties;
import tech.easyflow.ai.service.ResourceService;
import tech.easyflow.ai.utils.DocUtil;
import tech.easyflow.ai.utils.WorkFlowUtil;
import tech.easyflow.common.constant.enums.EnumResourceOriginType;
import tech.easyflow.common.cache.RedisIdempotencyExecutor;
import tech.easyflow.common.cache.RedisIdempotencyExecutor.IdempotentOperationInProgressException;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.filestorage.FileStorageManager;
import tech.easyflow.common.filestorage.FileStorageWriteHandle;
import tech.easyflow.common.filestorage.FileStorageWriteResult;
import tech.easyflow.common.util.SpringContextUtil;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;

public class DownloadNode extends BaseNode {
    private static final long serialVersionUID = 1L;

    private Integer resourceType;

    public DownloadNode() {
    }

    public DownloadNode(Integer resourceType) {
        this.resourceType = resourceType;
    }

    @Override
    public Map<String, Object> execute(Chain chain) {
        Map<String, Object> map =
                chain.getExecutionState().resolveParameters(this);
        String originUrl = map.get("originUrl").toString();
        LoginAccount account = WorkFlowUtil.getOperator(chain);
        ResourceService resourceService =
                SpringContextUtil.getBean(ResourceService.class);
        String idempotencyKey =
                chain.currentExecutionIdempotencyKey(this.id);
        String resourceName = idempotencyKey == null
                ? IdUtil.simpleUUID()
                : UUID.nameUUIDFromBytes(
                idempotencyKey.getBytes(StandardCharsets.UTF_8))
                .toString()
                .replace("-", "");

        Resource existing = findResource(
                resourceService, resourceName, account);
        if (existing != null) {
            return output(existing.getResourceUrl());
        }

        WorkflowRuntimeProperties runtimeProperties =
                SpringContextUtil.getBean(WorkflowRuntimeProperties.class);
        AtomicReference<String> resourceUrl =
                new AtomicReference<>();
        if (idempotencyKey == null) {
            resourceUrl.set(downloadAndPersist(
                    originUrl,
                    resourceName,
                    account,
                    resourceService,
                    runtimeProperties));
        } else {
            RedisIdempotencyExecutor idempotencyExecutor =
                    SpringContextUtil.getBean(
                            RedisIdempotencyExecutor.class);
            boolean executed;
            try {
                executed = idempotencyExecutor.executeOnce(
                        idempotencyKey + ":download-resource",
                        () -> resourceUrl.set(downloadAndPersist(
                                originUrl,
                                resourceName,
                                account,
                                resourceService,
                                runtimeProperties)));
            } catch (IdempotentOperationInProgressException conflict) {
                throw new RetryableTriggerException(
                        "下载幂等操作仍在处理中", conflict);
            }
            if (!executed) {
                Resource completed = findResource(
                        resourceService, resourceName, account);
                if (completed == null) {
                    throw new IllegalStateException(
                            "Download idempotency receipt exists "
                                    + "without resource record");
                }
                resourceUrl.set(completed.getResourceUrl());
            }
        }
        return output(resourceUrl.get());
    }

    /**
     * 流式下载、稳定上传并持久化素材记录。
     *
     * @param originUrl 原始 URL
     * @param resourceName 稳定资源名称
     * @param account 操作账号
     * @param resourceService 素材服务
     * @param runtimeProperties 工作流运行配置
     * @return 素材 URL
     */
    private String downloadAndPersist(
            String originUrl,
            String resourceName,
            LoginAccount account,
            ResourceService resourceService,
            WorkflowRuntimeProperties runtimeProperties) {
        try (DocUtil.DownloadedFile downloadedFile =
                     DocUtil.downloadFileToTemp(originUrl, runtimeProperties.getDownloadMaxBytes())) {
            String suffix = FileTypeUtil.getType(downloadedFile.path().toFile());
            if (suffix == null) {
                suffix = "unknown";
            }

            String fileName = resourceName + "." + suffix;
            FileStorageManager manager = SpringContextUtil.getBean(FileStorageManager.class);
            FileStorageWriteHandle handle =
                    manager.prepareRecoverableWrite(
                            "workflow/download", fileName);
            boolean existedBefore = manager.existsRecoverable(handle);
            FileStorageWriteResult writeResult;
            try {
                try (IoBulkhead.Permit ignored =
                             IoBulkhead.storage().acquire(
                                     "storage:upload")) {
                    writeResult = manager.saveRecoverable(
                            new TemporaryFileMultipartFile(
                                    fileName,
                                    downloadedFile.path(),
                                    downloadedFile.contentType()),
                            handle);
                }

                Resource alreadySaved = findResource(
                        resourceService, resourceName, account);
                if (alreadySaved != null) {
                    return alreadySaved.getResourceUrl();
                }
                Resource resource = new Resource();
                resource.setDeptId(account.getDeptId());
                resource.setTenantId(account.getTenantId());
                resource.setResourceType(this.resourceType);
                resource.setResourceName(resourceName);
                resource.setSuffix(suffix);
                resource.setResourceUrl(writeResult.getUrl());
                resource.setOrigin(
                        EnumResourceOriginType.GENERATE.getCode());
                resource.setCreated(new Date());
                resource.setCreatedBy(account.getId());
                resource.setModified(new Date());
                resource.setModifiedBy(account.getId());
                resource.setFileSize(
                        BigInteger.valueOf(downloadedFile.size()));
                try {
                    TenantManager.ignoreTenantCondition();
                    if (!resourceService.save(resource)) {
                        throw new IllegalStateException(
                                "素材记录保存失败");
                    }
                } finally {
                    TenantManager.restoreTenantCondition();
                }
                return writeResult.getUrl();
            } catch (RuntimeException | Error error) {
                if (!existedBefore) {
                    try {
                        manager.deleteRecoverable(handle);
                    } catch (RuntimeException cleanupError) {
                        error.addSuppressed(cleanupError);
                    }
                }
                throw error;
            }
        }
    }

    /**
     * 查询同一稳定执行生成的素材记录。
     *
     * @param service 素材服务
     * @param resourceName 稳定资源名
     * @param account 操作账号
     * @return 已存在记录
     */
    private Resource findResource(
            ResourceService service,
            String resourceName,
            LoginAccount account) {
        try {
            TenantManager.ignoreTenantCondition();
            return service.getOne(QueryWrapper.create()
                    .where(Resource::getResourceName)
                    .eq(resourceName)
                    .and(Resource::getTenantId)
                    .eq(account.getTenantId())
                    .and(Resource::getResourceType)
                    .eq(this.resourceType));
        } finally {
            TenantManager.restoreTenantCondition();
        }
    }

    /**
     * 按节点定义的输出名称返回资源 URL。
     *
     * @param resourceUrl 资源 URL
     * @return 节点输出
     */
    private Map<String, Object> output(String resourceUrl) {
        Map<String, Object> result = new HashMap<>();
        String key = "resourceUrl";
        List<Parameter> outputDefs = getOutputDefs();
        if (outputDefs != null && !outputDefs.isEmpty()) {
            String defName = outputDefs.get(0).getName();
            if (StringUtil.hasText(defName)) {
                key = defName;
            }
        }
        result.put(key, resourceUrl);
        return result;
    }

    public Integer getResourceType() {
        return resourceType;
    }

    public void setResourceType(Integer resourceType) {
        this.resourceType = resourceType;
    }
}
