package tech.easyflow.skill.service.impl;

import com.easyagents.skill.exception.SkillException;
import com.easyagents.skill.factory.SkillFactory;
import com.easyagents.skill.validation.defaults.DefaultSkillValidator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.entity.SkillAsset;
import tech.easyflow.skill.entity.SkillReference;
import tech.easyflow.skill.entity.SkillScript;
import tech.easyflow.skill.mapper.SkillMapper;
import tech.easyflow.skill.service.*;
import tech.easyflow.skill.support.SkillModelConverter;
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.enums.VisibilityScope;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.ResourceAccessService;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Skill 业务服务实现。
 */
@Service
public class SkillServiceImpl extends ServiceImpl<SkillMapper, Skill> implements SkillService {

    private final DefaultSkillValidator skillValidator = new DefaultSkillValidator();

    @Resource
    private SkillCategoryService skillCategoryService;
    @Resource
    private SkillReferenceService skillReferenceService;
    @Resource
    private SkillScriptService skillScriptService;
    @Resource
    private SkillAssetService skillAssetService;
    @Resource
    private ResourceAccessService resourceAccessService;
    @Resource
    private CategoryPermissionService categoryPermissionService;
    @Resource
    private ObjectMapper objectMapper;

    /**
     * {@inheritDoc}
     */
    @Override
    public Skill getDetail(BigInteger id) {
        Skill skill = requireSkill(id);
        resourceAccessService.assertAccess(CategoryResourceType.SKILL, skill, ResourceAction.READ, "无权限查看该 Skill");
        fillResources(skill);
        return skill;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Skill saveDraft(Skill skill) {
        validateDraft(skill);
        applyDraftDefaults(skill);
        save(skill);
        replaceResources(skill);
        return getDetail(skill.getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Skill updateDraft(Skill skill) {
        if (skill == null || skill.getId() == null) {
            throw new BusinessException("Skill ID 不能为空");
        }
        Skill existing = requireSkill(skill.getId());
        resourceAccessService.assertAccess(CategoryResourceType.SKILL, existing, ResourceAction.MANAGE, "无权限管理该 Skill");
        validateDraft(skill);
        applyDraftUpdate(existing, skill);
        updateById(existing);
        replaceResources(existing);
        return getDetail(existing.getId());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<String, Object> buildPublishSnapshot(Skill skill) {
        Skill detail = getDetail(skill.getId());
        com.easyagents.skill.model.Skill agentSkill = SkillModelConverter.toAgentSkill(detail);
        Map<String, Object> snapshot = new LinkedHashMap<>();
        snapshot.put("id", detail.getId());
        snapshot.put("tenantId", detail.getTenantId());
        snapshot.put("deptId", detail.getDeptId());
        snapshot.put("createdBy", detail.getCreatedBy());
        snapshot.put("categoryId", detail.getCategoryId());
        snapshot.put("name", detail.getName());
        snapshot.put("displayName", detail.getDisplayName());
        snapshot.put("description", detail.getDescription());
        snapshot.put("metadataJson", detail.getMetadataJson());
        snapshot.put("skillContent", detail.getSkillContent());
        snapshot.put("enabled", detail.getEnabled());
        snapshot.put("visibilityScope", detail.getVisibilityScope());
        snapshot.put("sourceType", detail.getSourceType());
        snapshot.put("packageHash", detail.getPackageHash());
        snapshot.put("references", agentSkill.getReferences());
        snapshot.put("scripts", agentSkill.getScripts());
        snapshot.put("assets", agentSkill.getAssets());
        snapshot.put("snapshotAt", new Date());
        return snapshot;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Skill fromSnapshot(Map<String, Object> snapshot) {
        if (snapshot == null || snapshot.isEmpty()) {
            throw new BusinessException("Skill 发布快照为空");
        }
        Skill skill = objectMapper.convertValue(snapshot, Skill.class);
        skill.setId(toBigInteger(snapshot.get("id")));
        skill.setTenantId(toBigInteger(snapshot.get("tenantId")));
        skill.setDeptId(toBigInteger(snapshot.get("deptId")));
        skill.setCreatedBy(toBigInteger(snapshot.get("createdBy")));
        skill.setCategoryId(toBigInteger(snapshot.get("categoryId")));
        skill.setPublishStatus(PublishStatus.PUBLISHED.getCode());
        skill.setPublishedSnapshotJson(snapshot);
        return skill;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeAggregate(BigInteger id) {
        if (id == null) {
            return;
        }
        removeResources(id);
        removeById(id);
    }

    private Skill requireSkill(BigInteger id) {
        Skill skill = getById(id);
        if (skill == null) {
            throw new BusinessException("Skill 不存在");
        }
        return skill;
    }

    private void validateDraft(Skill skill) {
        if (skill == null) {
            throw new BusinessException("Skill 不能为空");
        }
        normalizeFromSkillContent(skill);
        if (skill.getName() == null || skill.getName().isBlank()) {
            throw new BusinessException("Skill 名称不能为空");
        }
        if (skill.getDescription() == null || skill.getDescription().isBlank()) {
            throw new BusinessException("Skill 描述不能为空");
        }
        if (skill.getSkillContent() == null || skill.getSkillContent().isBlank()) {
            throw new BusinessException("SKILL.md 内容不能为空");
        }
        skillCategoryService.validateUsableCategory(skill.getCategoryId());
        validateTargetCategoryVisible(skill.getCategoryId());
        skill.setVisibilityScope(VisibilityScope.fromOrDefault(skill.getVisibilityScope(), VisibilityScope.PRIVATE).name());
        validateSkillPackage(skill);
    }

    private void validateTargetCategoryVisible(BigInteger categoryId) {
        if (categoryId == null) {
            return;
        }
        RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess(CategoryResourceType.SKILL.getCode());
        if (access.isRestricted() && !access.getCategoryIds().contains(categoryId)) {
            throw new BusinessException("无权限使用该 Skill 分类");
        }
    }

    private void applyDraftDefaults(Skill skill) {
        LoginAccount account = requireCurrentLoginAccount();
        Date now = new Date();
        skill.setTenantId(account.getTenantId());
        skill.setDeptId(account.getDeptId());
        skill.setCreated(now);
        skill.setCreatedBy(account.getId());
        skill.setModified(now);
        skill.setModifiedBy(account.getId());
        skill.setEnabled(skill.getEnabled() == null || skill.getEnabled());
        skill.setSourceType(skill.getSourceType() == null ? "MANUAL" : skill.getSourceType());
        skill.setPublishStatus(PublishStatus.DRAFT.getCode());
        syncCounts(skill);
    }

    private void applyDraftUpdate(Skill existing, Skill incoming) {
        LoginAccount account = requireCurrentLoginAccount();
        existing.setCategoryId(incoming.getCategoryId());
        existing.setName(incoming.getName());
        existing.setDisplayName(incoming.getDisplayName());
        existing.setDescription(incoming.getDescription());
        existing.setMetadataJson(incoming.getMetadataJson());
        existing.setSkillContent(incoming.getSkillContent());
        existing.setEnabled(incoming.getEnabled() == null || incoming.getEnabled());
        existing.setVisibilityScope(incoming.getVisibilityScope());
        existing.setSourceType(incoming.getSourceType());
        existing.setPackageHash(incoming.getPackageHash());
        existing.setReferences(incoming.getReferences());
        existing.setScripts(incoming.getScripts());
        existing.setAssets(incoming.getAssets());
        syncCounts(existing);
        existing.setModified(new Date());
        existing.setModifiedBy(account.getId());
    }

    private void syncCounts(Skill skill) {
        skill.setReferenceCount(skill.getReferences() == null ? 0 : skill.getReferences().size());
        skill.setScriptCount(skill.getScripts() == null ? 0 : skill.getScripts().size());
        skill.setAssetCount(skill.getAssets() == null ? 0 : skill.getAssets().size());
    }

    private void fillResources(Skill skill) {
        skill.setReferences(skillReferenceService.list(QueryWrapper.create().eq(SkillReference::getSkillId, skill.getId()).orderBy("path asc")));
        skill.setScripts(skillScriptService.list(QueryWrapper.create().eq(SkillScript::getSkillId, skill.getId()).orderBy("path asc")));
        skill.setAssets(skillAssetService.list(QueryWrapper.create().eq(SkillAsset::getSkillId, skill.getId()).orderBy("path asc")));
    }

    private void replaceResources(Skill skill) {
        removeResources(skill.getId());
        BigInteger tenantId = skill.getTenantId();
        BigInteger skillId = skill.getId();
        if (skill.getReferences() != null) {
            for (SkillReference reference : skill.getReferences()) {
                reference.setTenantId(tenantId);
                reference.setSkillId(skillId);
                skillReferenceService.save(reference);
            }
        }
        if (skill.getScripts() != null) {
            for (SkillScript script : skill.getScripts()) {
                script.setTenantId(tenantId);
                script.setSkillId(skillId);
                skillScriptService.save(script);
            }
        }
        if (skill.getAssets() != null) {
            for (SkillAsset asset : skill.getAssets()) {
                asset.setTenantId(tenantId);
                asset.setSkillId(skillId);
                skillAssetService.save(asset);
            }
        }
    }

    private void removeResources(BigInteger skillId) {
        skillReferenceService.remove(QueryWrapper.create().eq(SkillReference::getSkillId, skillId));
        skillScriptService.remove(QueryWrapper.create().eq(SkillScript::getSkillId, skillId));
        skillAssetService.remove(QueryWrapper.create().eq(SkillAsset::getSkillId, skillId));
    }

    private void normalizeFromSkillContent(Skill skill) {
        try {
            com.easyagents.skill.model.Skill parsed = SkillFactory.create(
                    skill.getId() == null ? "draft" : String.valueOf(skill.getId()),
                    skill.getSkillContent(),
                    SkillModelConverter.toAgentReferences(skill.getReferences()),
                    SkillModelConverter.toAgentScripts(skill.getScripts()),
                    SkillModelConverter.toAgentAssets(skill.getAssets())
            );
            skill.setName(parsed.getName());
            if (skill.getDisplayName() == null || skill.getDisplayName().isBlank()) {
                skill.setDisplayName(parsed.getName());
            }
            skill.setDescription(parsed.getDescription());
            skill.setMetadataJson(parsed.getMetadata().getValues());
        } catch (SkillException e) {
            throw new BusinessException("SKILL.md frontmatter 不合法：" + e.getMessage());
        }
    }

    private void validateSkillPackage(Skill skill) {
        try {
            skillValidator.validate(SkillModelConverter.toAgentSkill(skill));
        } catch (SkillException e) {
            throw new BusinessException("Skill 包校验失败：" + e.getMessage());
        }
    }

    private LoginAccount requireCurrentLoginAccount() {
        LoginAccount account = SaTokenUtil.getLoginAccount();
        if (account == null || account.getId() == null) {
            throw new BusinessException("未登录或登录态无效");
        }
        return account;
    }

    private BigInteger toBigInteger(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof BigInteger bigInteger) {
            return bigInteger;
        }
        if (value instanceof Number number) {
            return BigInteger.valueOf(number.longValue());
        }
        return new BigInteger(String.valueOf(value));
    }
}
