package com.ttstd.controller.webrtc;

import android.content.ContentValues;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;

import org.webrtc.VideoFrame;
import org.webrtc.VideoSink;

import java.io.File;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 将远控端收到的视频帧编码为 MP4 文件保存。
 *
 * 通过实现 {@link VideoSink} 接收 WebRTC 解码后的视频帧（I420），
 * 也可通过 {@link #submitDecodedFrame(byte[], int, int, long)} 接收自编码模式解码出的帧，
 * 统一转换为 NV12 后交由 MediaCodec 编码器与 MediaMuxer 写盘。
 *
 * 编码与写盘均在单一后台线程执行，避免阻塞 WebRTC 网络/解码线程。
 */
public class VideoRecorder implements VideoSink {

    private static final String TAG = "VideoRecorder";
    private static final String FOLDER_NAME = "WebRTCRecordings";
    private static final long TIMEOUT_US = 10000;

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private volatile Context appContext;
    private volatile boolean recording = false;

    // 仅由后台线程访问
    private MediaCodec encoder;
    private MediaMuxer muxer;
    private int trackIndex = -1;
    private boolean muxerStarted = false;
    private int framesWritten = 0;
    private Uri mediaStoreUri = null;
    private String lastFilePath = null;
    private long startPtsUs = -1;
    private boolean initErrorShown = false;

    // 编码器输入颜色格式（在 start 时选定，整段录制共用）
    private int inputColorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
    private boolean planarInput = false;

    private OnRecordingListener listener;

    public interface OnRecordingListener {
        void onStarted();

        /** 录制成功，savedLocation 为保存路径或 Uri。 */
        void onStopped(String savedLocation);

        /** 录制已停止，但没有写入任何画面（例如未收到帧即停止）。 */
        void onStoppedEmpty();

        /** 真正的错误（如编码器/封装初始化失败）。 */
        void onError(String message);
    }

    public void setOnRecordingListener(OnRecordingListener l) {
        this.listener = l;
    }

    public boolean isRecording() {
        return recording;
    }

    /** 开始录制。真正的编码器与文件会在收到第一帧（确定分辨率）时初始化。 */
    public void start(Context context) {
        if (recording) return;
        this.appContext = context.getApplicationContext();
        this.recording = true;
        this.startPtsUs = -1;
        this.framesWritten = 0;
        this.initErrorShown = false;
        // 提前选定编码器输入颜色格式，避免首帧时尚未确定导致帧格式错乱
        this.inputColorFormat = selectColorFormat(MediaFormat.MIMETYPE_VIDEO_AVC);
        this.planarInput = (inputColorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
        if (listener != null) listener.onStarted();
    }

    /** 停止录制并保存文件。 */
    public void stop() {
        if (!recording) return;
        recording = false;
        executor.execute(this::finalizeRecording);
    }

    /** WebRTC 模式：接收解码后的视频帧。 */
    @Override
    public void onFrame(VideoFrame frame) {
        if (!recording) return;
        VideoFrame.I420Buffer i420 = frame.getBuffer().toI420();
        int w = i420.getWidth();
        int h = i420.getHeight();
        if (w <= 0 || h <= 0) {
            i420.release();
            return;
        }
        byte[] data = planarInput ? i420ToI420(i420) : i420ToNv12(i420);
        i420.release();
        long ptsUs = frame.getTimestampNs() / 1000;
        executor.execute(() -> encodeFrameInternal(data, w, h, ptsUs));
    }

    /** 自编码模式：接收解码器输出并已转换为 NV12 的帧。 */
    public void submitDecodedFrame(byte[] nv12, int w, int h, long ptsUs) {
        if (!recording || nv12 == null) return;
        byte[] data = planarInput ? nv12ToI420(nv12, w, h) : nv12;
        executor.execute(() -> encodeFrameInternal(data, w, h, ptsUs));
    }

    private void encodeFrameInternal(byte[] nv12, int w, int h, long ptsUs) {
        if (encoder == null) {
            if (!initEncoder(w, h)) {
                Log.e(TAG, "编码器初始化失败，停止录制");
                return;
            }
        }
        if (startPtsUs < 0) startPtsUs = ptsUs;
        long sampleTime = ptsUs - startPtsUs;
        if (sampleTime < 0) sampleTime = 0;

        try {
            int inIdx = encoder.dequeueInputBuffer(TIMEOUT_US);
            if (inIdx >= 0) {
                ByteBuffer inBuf = encoder.getInputBuffer(inIdx);
                if (inBuf != null) {
                    inBuf.clear();
                    inBuf.put(nv12);
                    encoder.queueInputBuffer(inIdx, 0, nv12.length, sampleTime, 0);
                }
            }
            drainEncoder(false);
        } catch (Exception e) {
            Log.e(TAG, "编码异常", e);
        }
    }

    private boolean initEncoder(int w, int h) {
        try {
            int bitrate = (int) (w * h * 30 * 0.15);
            bitrate = Math.max(1_000_000, Math.min(bitrate, 8_000_000));

            MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, w, h);
            format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, inputColorFormat);
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);

            encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            encoder.start();

            if (!initMuxer()) {
                encoder.release();
                encoder = null;
                return false;
            }
            return true;
        } catch (Exception e) {
            Log.e(TAG, "initEncoder 错误", e);
            if (listener != null && !initErrorShown) {
                initErrorShown = true;
                listener.onError("录制初始化失败: " + e.getMessage());
            }
            return false;
        }
    }

    private boolean initMuxer() {
        try {
            ContentValues values = new ContentValues();
            String fileName = "webrtc_record_" + System.currentTimeMillis() + ".mp4";
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                values.put(MediaStore.Video.Media.RELATIVE_PATH,
                        Environment.DIRECTORY_MOVIES + "/" + FOLDER_NAME);
                values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);
                values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
                values.put(MediaStore.Video.Media.IS_PENDING, 1);
                mediaStoreUri = appContext.getContentResolver()
                        .insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
                if (mediaStoreUri == null) return false;
                ParcelFileDescriptor pfd = appContext.getContentResolver()
                        .openFileDescriptor(mediaStoreUri, "rw");
                if (pfd == null) return false;
                muxer = new MediaMuxer(pfd.getFileDescriptor(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            } else {
                File dir = new File(Environment.getExternalStoragePublicDirectory(
                        Environment.DIRECTORY_MOVIES), FOLDER_NAME);
                if (!dir.exists() && !dir.mkdirs()) return false;
                File file = new File(dir, fileName);
                muxer = new MediaMuxer(file.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                lastFilePath = file.getAbsolutePath();
            }
            return true;
        } catch (Exception e) {
            Log.e(TAG, "initMuxer 错误", e);
            return false;
        }
    }

    private void drainEncoder(boolean endOfStream) {
        if (endOfStream) {
            try {
                int inIdx = encoder.dequeueInputBuffer(TIMEOUT_US);
                if (inIdx >= 0) {
                    encoder.queueInputBuffer(inIdx, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                }
            } catch (Exception ignored) {
            }
        }
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        while (true) {
            int outIdx = encoder.dequeueOutputBuffer(info, TIMEOUT_US);
            if (outIdx == MediaCodec.INFO_TRY_AGAIN_LATER) break;
            if (outIdx == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                if (muxer != null && !muxerStarted) {
                    trackIndex = muxer.addTrack(encoder.getOutputFormat());
                    muxer.start();
                    muxerStarted = true;
                    Log.d(TAG, "Muxer 已开始，视频轨道: " + trackIndex);
                }
                continue;
            }
            if (outIdx < 0) continue;
            try {
                ByteBuffer outBuf = encoder.getOutputBuffer(outIdx);
                if (outBuf != null && info.size > 0 && muxerStarted
                        && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
                    outBuf.position(info.offset);
                    outBuf.limit(info.offset + info.size);
                    muxer.writeSampleData(trackIndex, outBuf, info);
                    framesWritten++;
                }
            } catch (Exception e) {
                Log.e(TAG, "writeSampleData 错误", e);
            } finally {
                encoder.releaseOutputBuffer(outIdx, false);
            }
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) break;
        }
    }

    private void finalizeRecording() {
        // 先保存状态，避免下方置空后误判
        boolean wasStarted = muxerStarted;
        int written = framesWritten;
        Uri msUri = mediaStoreUri;
        String fp = lastFilePath;

        try {
            if (encoder != null) drainEncoder(true);
        } catch (Exception e) {
            Log.e(TAG, "收尾 drain 错误", e);
        }
        try {
            if (muxer != null && muxerStarted) muxer.stop();
        } catch (Exception e) {
            Log.e(TAG, "muxer stop 错误", e);
        }
        try {
            if (muxer != null) muxer.release();
        } catch (Exception ignored) {
        }
        try {
            if (encoder != null) encoder.release();
        } catch (Exception ignored) {
        }
        encoder = null;
        muxer = null;
        muxerStarted = false;
        mediaStoreUri = null;
        lastFilePath = null;
        startPtsUs = -1;

        if (listener == null) return;

        boolean hasContent = wasStarted && written > 0;
        if (hasContent) {
            if (msUri != null) {
                ContentValues v = new ContentValues();
                v.put(MediaStore.Video.Media.IS_PENDING, 0);
                appContext.getContentResolver().update(msUri, v, null, null);
                listener.onStopped("已保存至 相册/Movies/" + FOLDER_NAME);
            } else if (fp != null) {
                listener.onStopped(fp);
            } else {
                listener.onStopped("已保存");
            }
        } else {
            // 没有写入任何画面：清理占位的媒体库记录或空文件
            if (msUri != null) {
                try {
                    appContext.getContentResolver().delete(msUri, null, null);
                } catch (Exception ignored) {
                }
            }
            if (fp != null) {
                try {
                    new File(fp).delete();
                } catch (Exception ignored) {
                }
            }
            listener.onStoppedEmpty();
        }
    }

    private static int selectColorFormat(String mimeType) {
        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
        // 优选顺序：NV12(SemiPlanar) -> I420(Planar) -> Flexible
        // 避免直接选 Flexible：很多设备会把 NV12 输入的色度平面读错位置，
        // 导致只剩亮度平面，画面变成黑白、颜色对不上。
        int[] preferred = new int[]{
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
        };
        int fallback = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
        int chosen = -1;
        for (MediaCodecInfo info : list.getCodecInfos()) {
            if (!info.isEncoder()) continue;
            try {
                for (String type : info.getSupportedTypes()) {
                    if (type.equalsIgnoreCase(mimeType)) {
                        MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(type);
                        // 按优选顺序找到第一个受支持的格式
                        for (int want : preferred) {
                            for (int cf : caps.colorFormats) {
                                if (cf == want) {
                                    if (chosen < 0 || preferRank(want) < preferRank(chosen)) {
                                        chosen = want;
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            } catch (Exception ignored) {
            }
        }
        return chosen > 0 ? chosen : fallback;
    }

    private static int preferRank(int cf) {
        if (cf == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) return 0;
        if (cf == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) return 1;
        return 2;
    }

    private static byte[] i420ToNv12(VideoFrame.I420Buffer i420) {
        int w = i420.getWidth();
        int h = i420.getHeight();
        int ySize = w * h;
        int uvSize = w * h / 2;
        byte[] nv12 = new byte[ySize + uvSize];

        ByteBuffer dataY = i420.getDataY();
        ByteBuffer dataU = i420.getDataU();
        ByteBuffer dataV = i420.getDataV();
        int strideY = i420.getStrideY();
        int strideU = i420.getStrideU();
        int strideV = i420.getStrideV();

        // Y 平面
        for (int y = 0; y < h; y++) {
            dataY.position(y * strideY);
            dataY.get(nv12, y * w, w);
        }

        // U/V 交织成 NV12
        int halfH = h / 2;
        int halfW = w / 2;
        byte[] uRow = new byte[halfW];
        byte[] vRow = new byte[halfW];
        int pos = ySize;
        for (int y = 0; y < halfH; y++) {
            dataU.position(y * strideU);
            dataU.get(uRow, 0, halfW);
            dataV.position(y * strideV);
            dataV.get(vRow, 0, halfW);
            for (int x = 0; x < halfW; x++) {
                nv12[pos++] = uRow[x];
                nv12[pos++] = vRow[x];
            }
        }
        return nv12;
    }

    /** I420 转 I420（抽掉各平面 stride 填充，输出紧凑 packed 布局）。 */
    private static byte[] i420ToI420(VideoFrame.I420Buffer i420) {
        int w = i420.getWidth();
        int h = i420.getHeight();
        int ySize = w * h;
        int uvSize = w * h / 4;
        byte[] out = new byte[ySize + uvSize * 2];

        ByteBuffer dataY = i420.getDataY();
        ByteBuffer dataU = i420.getDataU();
        ByteBuffer dataV = i420.getDataV();
        int strideY = i420.getStrideY();
        int strideU = i420.getStrideU();
        int strideV = i420.getStrideV();
        int halfW = w / 2;
        int halfH = h / 2;

        int pos = 0;
        for (int y = 0; y < h; y++) {
            dataY.position(y * strideY);
            dataY.get(out, pos, w);
            pos += w;
        }
        for (int y = 0; y < halfH; y++) {
            dataU.position(y * strideU);
            dataU.get(out, pos, halfW);
            pos += halfW;
        }
        for (int y = 0; y < halfH; y++) {
            dataV.position(y * strideV);
            dataV.get(out, pos, halfW);
            pos += halfW;
        }
        return out;
    }

    /** NV12 转 I420（抽掉 UV 交织，输出紧凑 I420 布局）。 */
    private static byte[] nv12ToI420(byte[] nv12, int w, int h) {
        int ySize = w * h;
        int halfW = w / 2;
        int halfH = h / 2;
        int uvPixels = halfW * halfH;
        byte[] out = new byte[ySize + uvPixels * 2];
        System.arraycopy(nv12, 0, out, 0, ySize);
        int uvOff = ySize;
        int uPos = ySize;
        int vPos = ySize + uvPixels;
        for (int i = 0; i < uvPixels; i++) {
            out[uPos + i] = nv12[uvOff + i * 2];
            out[vPos + i] = nv12[uvOff + i * 2 + 1];
        }
        return out;
    }
}
