package com.starry.admin.modules.weichat.service;

import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.common.oss.service.IOssFileService;
import com.starry.admin.modules.clerk.enums.ClerkMediaUsage;
import com.starry.admin.modules.clerk.module.entity.PlayClerkMediaAssetEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkMediaAssetService;
import com.starry.admin.modules.media.entity.PlayMediaEntity;
import com.starry.admin.modules.media.enums.MediaKind;
import com.starry.admin.modules.media.enums.MediaOwnerType;
import com.starry.admin.modules.media.enums.MediaStatus;
import com.starry.admin.modules.media.service.IPlayMediaService;
import com.starry.admin.modules.weichat.assembler.ClerkMediaAssembler;
import com.starry.admin.modules.weichat.entity.clerk.MediaVo;
import com.starry.common.utils.IdUtils;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.imageio.ImageIO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import ws.schild.jave.Encoder;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.encode.AudioAttributes;
import ws.schild.jave.encode.EncodingAttributes;
import ws.schild.jave.encode.VideoAttributes;
import ws.schild.jave.info.MultimediaInfo;
import ws.schild.jave.info.VideoInfo;
import ws.schild.jave.info.VideoSize;

@Service
@RequiredArgsConstructor
@Slf4j
public class MediaUploadService {

    private static final long MAX_VIDEO_BYTES = 30L * 1024 * 1024;
    private static final long MAX_VIDEO_DURATION_MS = 45_000;
    private static final String IMAGE_OUTPUT_FORMAT = "image2";
    private static final String VIDEO_OUTPUT_FORMAT = "mp4";

    private final IOssFileService ossFileService;
    private final IPlayMediaService mediaService;
    private final IPlayClerkMediaAssetService clerkMediaAssetService;

    @Transactional(rollbackFor = Exception.class)
    public MediaVo upload(MultipartFile file, PlayClerkUserInfoEntity clerkInfo, ClerkMediaUsage usage) {
        if (file == null || file.isEmpty()) {
            throw new CustomException("请选择要上传的文件");
        }
        if (clerkInfo == null) {
            throw new CustomException("店员信息不存在");
        }

        String originalFilename = StrUtil.blankToDefault(file.getOriginalFilename(), file.getName());
        File tempFile = null;
        File processedVideoFile = null;
        File coverFile = null;
        try {
            String suffix = resolveSuffix(originalFilename);
            tempFile = createTempFile("media_", suffix);
            file.transferTo(tempFile);

            String detectedType = detectFileType(tempFile, file.getContentType());
            boolean isVideo = isVideoType(detectedType, file.getContentType());
            boolean isImage = isImageType(detectedType, file.getContentType());
            if (!isVideo && !isImage) {
                log.warn("Unsupported media type: {} / {}", detectedType, file.getContentType());
                throw new CustomException("不支持的文件格式");
            }

            PlayMediaEntity entity = buildSkeletonEntity(file, clerkInfo,
                    isVideo ? MediaKind.VIDEO : MediaKind.IMAGE);
            entity.getMetadata().put("detectedType", detectedType);
            entity.getMetadata().put("isVideo", isVideo);

            if (isImage) {
                handleImageUpload(tempFile, entity, clerkInfo, originalFilename);
            } else {
                processedVideoFile = createTempFile("media_video_", ".mp4");
                coverFile = createTempFile("media_cover_", ".jpg");
                handleVideoUpload(tempFile, processedVideoFile, coverFile, entity, clerkInfo, originalFilename);
            }
            entity.setStatus(MediaStatus.READY.getCode());
            mediaService.normalizeAndSave(entity);
            PlayClerkMediaAssetEntity asset = clerkMediaAssetService.linkDraftAsset(
                    clerkInfo.getTenantId(),
                    clerkInfo.getId(),
                    entity.getId(),
                    usage == null ? ClerkMediaUsage.PROFILE : usage);
            return ClerkMediaAssembler.toVo(entity, asset);
        } catch (CustomException customException) {
            throw customException;
        } catch (Exception ex) {
            log.error("媒资上传失败", ex);
            throw new CustomException("媒资上传失败，请稍后重试");
        } finally {
            deleteQuietly(tempFile);
            deleteQuietly(processedVideoFile);
            deleteQuietly(coverFile);
        }
    }

    private PlayMediaEntity buildSkeletonEntity(MultipartFile file, PlayClerkUserInfoEntity clerkInfo, MediaKind kind) {
        PlayMediaEntity entity = new PlayMediaEntity();
        entity.setId(IdUtils.getUuid());
        entity.setTenantId(clerkInfo.getTenantId());
        entity.setOwnerType(MediaOwnerType.CLERK);
        entity.setOwnerId(clerkInfo.getId());
        entity.setKind(kind.getCode());
        entity.setStatus(MediaStatus.UPLOADED.getCode());
        entity.setSizeBytes(file.getSize());
        Map<String, Object> metadata = new HashMap<>();
        metadata.put("originalFilename", file.getOriginalFilename());
        metadata.put("contentType", file.getContentType());
        metadata.put("uploadTraceId", IdUtil.fastUUID());
        metadata.put("sourceSizeBytes", file.getSize());
        entity.setMetadata(metadata);
        return entity;
    }

    private void handleImageUpload(File tempFile, PlayMediaEntity entity, PlayClerkUserInfoEntity clerkInfo,
            String originalFilename) throws IOException {
        BufferedImage image = ImageIO.read(tempFile);
        if (image == null) {
            throw new CustomException("图片文件已损坏或格式不受支持");
        }
        entity.setWidth(image.getWidth());
        entity.setHeight(image.getHeight());
        try (InputStream is = Files.newInputStream(tempFile.toPath())) {
            String targetName = buildObjectName("img", originalFilename);
            String url = ossFileService.upload(is, clerkInfo.getTenantId(), targetName);
            entity.setUrl(url);
        }
    }

    private void handleVideoUpload(File sourceFile, File targetFile, File coverFile, PlayMediaEntity entity,
            PlayClerkUserInfoEntity clerkInfo, String originalFilename) throws Exception {
        if (entity.getSizeBytes() != null && entity.getSizeBytes() > MAX_VIDEO_BYTES) {
            throw new CustomException("视频大小不能超过30MB");
        }
        MultimediaObject multimediaObject = new MultimediaObject(sourceFile);
        MultimediaInfo info = multimediaObject.getInfo();
        if (info == null || info.getVideo() == null) {
            throw new CustomException("无法读取视频信息");
        }
        long durationMs = info.getDuration();
        if (durationMs > MAX_VIDEO_DURATION_MS) {
            throw new CustomException("视频时长不能超过45秒");
        }
        VideoInfo videoInfo = info.getVideo();
        VideoSize size = videoInfo.getSize();
        if (size != null) {
            entity.setWidth(size.getWidth());
            entity.setHeight(size.getHeight());
        }
        entity.setDurationMs(durationMs);

        AudioAttributes audioAttrs = new AudioAttributes();
        audioAttrs.setCodec("aac");
        audioAttrs.setBitRate(128_000);
        audioAttrs.setChannels(2);
        audioAttrs.setSamplingRate(44_100);

        VideoAttributes videoAttrs = new VideoAttributes();
        videoAttrs.setCodec("h264");
        videoAttrs.setBitRate(1_500_000);
        if (size != null) {
            videoAttrs.setSize(size);
        }
        float frameRate = videoInfo.getFrameRate();
        videoAttrs.setFrameRate(frameRate > 0 ? Math.round(frameRate) : 30);

        Encoder encoder = new Encoder();
        EncodingAttributes attrs = new EncodingAttributes();
        attrs.setOutputFormat(VIDEO_OUTPUT_FORMAT);
        attrs.setAudioAttributes(audioAttrs);
        attrs.setVideoAttributes(videoAttrs);
        encoder.encode(multimediaObject, targetFile, attrs);

        long processedSize = targetFile.length();
        entity.setSizeBytes(processedSize);

        // 抽取首帧作为封面
        EncodingAttributes coverAttrs = new EncodingAttributes();
        VideoAttributes coverVideoAttrs = new VideoAttributes();
        coverVideoAttrs.setCodec("mjpeg");
        if (size != null) {
            coverVideoAttrs.setSize(size);
        }
        coverAttrs.setOutputFormat(IMAGE_OUTPUT_FORMAT);
        coverAttrs.setVideoAttributes(coverVideoAttrs);
        coverAttrs.setDuration(0.01f);
        coverAttrs.setOffset(0f);
        coverAttrs.setAudioAttributes(null);
        encoder.encode(new MultimediaObject(targetFile), coverFile, coverAttrs);

        try (InputStream videoIs = Files.newInputStream(targetFile.toPath());
                InputStream coverIs = Files.newInputStream(coverFile.toPath())) {
            String videoName = buildObjectName("video", originalFilename);
            String coverName = buildObjectName("cover", originalFilename + ".jpg");
            String videoUrl = ossFileService.upload(videoIs, clerkInfo.getTenantId(), videoName);
            String coverUrl = ossFileService.upload(coverIs, clerkInfo.getTenantId(), coverName);
            entity.setUrl(videoUrl);
            entity.setCoverUrl(coverUrl);
        }
        if (entity.getMetadata() != null) {
            entity.getMetadata().put("durationMs", durationMs);
        }
    }

    private String detectFileType(File file, String contentType) {
        String type = null;
        try {
            type = FileTypeUtil.getType(file);
        } catch (Exception ex) {
            log.warn("Failed to read file type via signature, fallback to contentType: {}", contentType, ex);
        }
        if (StrUtil.isNotBlank(type)) {
            return type.toLowerCase(Locale.ROOT);
        }
        if (StrUtil.isNotBlank(contentType)) {
            return contentType.toLowerCase(Locale.ROOT);
        }
        return "";
    }

    private boolean isVideoType(String detectedType, String mime) {
        if (StrUtil.isBlank(detectedType) && StrUtil.isBlank(mime)) {
            return false;
        }
        String lower = StrUtil.blankToDefault(detectedType, "");
        if (lower.contains("mp4") || lower.contains("mov") || lower.contains("quicktime")) {
            return true;
        }
        String mimeLower = StrUtil.blankToDefault(mime, "").toLowerCase(Locale.ROOT);
        return mimeLower.startsWith("video/");
    }

    private boolean isImageType(String detectedType, String mime) {
        String lower = StrUtil.blankToDefault(detectedType, "");
        if (lower.contains("jpg") || lower.contains("jpeg") || lower.contains("png") || lower.contains("gif")
                || lower.contains("webp")) {
            return true;
        }
        String mimeLower = StrUtil.blankToDefault(mime, "").toLowerCase(Locale.ROOT);
        return mimeLower.startsWith("image/");
    }

    private String buildObjectName(String category, String originalFilename) {
        String ext = resolveSuffix(originalFilename);
        return StrUtil.join("/", "clerk", category, IdUtils.getUuid() + ext);
    }

    private String resolveSuffix(String filename) {
        if (StrUtil.isBlank(filename) || !filename.contains(".")) {
            return "";
        }
        return filename.substring(filename.lastIndexOf('.'));
    }

    private void deleteQuietly(File file) {
        if (file != null && file.exists()) {
            FileUtil.del(file);
        }
    }

    private File createTempFile(String prefix, String suffix) throws IOException {
        String effectiveSuffix = StrUtil.isBlank(suffix) ? ".tmp" : suffix;
        return Files.createTempFile(prefix, effectiveSuffix).toFile();
    }
}
