package com.lemenzo.gallery.video;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.media.MediaExtractor;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/** Lossless container trim/mute for MP4-compatible tracks. No video recompression. */
public final class VideoTrimExporter {
    private VideoTrimExporter() {}


    /** Returns the previous video sync sample in milliseconds for lossless trimming. */
    public static long previousSyncMs(Context context, Uri source, long requestedMs) throws IOException {
        MediaExtractor extractor = new MediaExtractor();
        try {
            extractor.setDataSource(context, source, null);
            int videoTrack = -1;
            for (int i = 0; i < extractor.getTrackCount(); i++) {
                MediaFormat f = extractor.getTrackFormat(i);
                String mime = f.getString(MediaFormat.KEY_MIME);
                if (mime != null && mime.startsWith("video/")) { videoTrack = i; break; }
            }
            if (videoTrack < 0) return Math.max(0L, requestedMs);
            extractor.selectTrack(videoTrack);
            extractor.seekTo(Math.max(0L, requestedMs) * 1000L, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
            long timeUs = extractor.getSampleTime();
            return timeUs < 0 ? Math.max(0L, requestedMs) : timeUs / 1000L;
        } finally {
            extractor.release();
        }
    }

    public static Uri export(Context context, Uri source, long startMs, long endMs, boolean mute) throws Exception {
        return export(context, source, startMs, endMs, mute, 0);
    }

    public static Uri export(Context context, Uri source, long startMs, long endMs, boolean mute, int rotationDelta) throws Exception {
        ContentResolver resolver = context.getContentResolver();
        ContentValues values = new ContentValues();
        String base = "Lemenzo_video";
        String relative = Environment.DIRECTORY_MOVIES + "/Lemenzo";
        String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.RELATIVE_PATH};
        try (Cursor c = resolver.query(source, projection, null, null, null)) {
            if (c != null && c.moveToFirst()) {
                String n = c.getString(0); if (n != null) { int dot = n.lastIndexOf('.'); base = dot > 0 ? n.substring(0, dot) : n; }
                String p = c.getString(1); if (p != null && !p.trim().isEmpty()) relative = p;
            }
        } catch (RuntimeException ignored) {}
        String stamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
        values.put(MediaStore.Video.Media.DISPLAY_NAME, base + "_edited_" + stamp + ".mp4");
        values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
        values.put(MediaStore.Video.Media.RELATIVE_PATH, relative);
        values.put(MediaStore.Video.Media.IS_PENDING, 1);
        Uri out = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
        if (out == null) throw new IOException("MediaStore insert failed");

        boolean success = false;
        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(out, "rw")) {
            if (pfd == null) throw new IOException("Cannot open output");
            MediaExtractor extractor = new MediaExtractor();
            MediaMuxer muxer = null;
            try {
                extractor.setDataSource(context, source, null);
                muxer = new MediaMuxer(pfd.getFileDescriptor(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                Map<Integer,Integer> tracks = new HashMap<>();
                int maxInput = 1024 * 1024;
                for (int i = 0; i < extractor.getTrackCount(); i++) {
                    MediaFormat f = extractor.getTrackFormat(i);
                    String mime = f.getString(MediaFormat.KEY_MIME);
                    boolean video = mime != null && mime.startsWith("video/");
                    boolean audio = mime != null && mime.startsWith("audio/");
                    if (video || (audio && !mute)) {
                        int outTrack = muxer.addTrack(f); tracks.put(i, outTrack); extractor.selectTrack(i);
                        if (f.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) maxInput = Math.max(maxInput, f.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE));
                    }
                }
                if (tracks.isEmpty()) throw new IOException("No supported tracks");
                MediaMetadataRetriever metadata = new MediaMetadataRetriever();
                try {
                    metadata.setDataSource(context, source);
                    String rotation = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
                    int originalRotation = rotation == null ? 0 : Integer.parseInt(rotation);
                    int finalRotation = ((originalRotation + rotationDelta) % 360 + 360) % 360;
                    muxer.setOrientationHint(finalRotation);
                } finally { metadata.release(); }
                muxer.start();
                long startUs = Math.max(0, startMs) * 1000L;
                long endUs = Math.max(startMs, endMs) * 1000L;
                extractor.seekTo(startUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
                long baseTimeUs = -1;
                ByteBuffer buffer = ByteBuffer.allocateDirect(Math.max(maxInput, 2 * 1024 * 1024));
                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                while (true) {
                    int inputTrack = extractor.getSampleTrackIndex();
                    if (inputTrack < 0) break;
                    long timeUs = extractor.getSampleTime();
                    if (timeUs < 0 || timeUs > endUs) break;
                    Integer outputTrack = tracks.get(inputTrack);
                    if (outputTrack != null) {
                        long sampleSize = extractor.getSampleSize();
                        if (sampleSize > buffer.capacity()) {
                            if (sampleSize > Integer.MAX_VALUE) throw new IOException("Video sample too large");
                            buffer = ByteBuffer.allocateDirect((int) sampleSize);
                        }
                        buffer.clear();
                        int size = extractor.readSampleData(buffer, 0);
                        if (size < 0) break;
                        if (baseTimeUs < 0) baseTimeUs = timeUs;
                        info.offset = 0; info.size = size;
                        info.presentationTimeUs = Math.max(0, timeUs - baseTimeUs);
                        info.flags = extractor.getSampleFlags();
                        muxer.writeSampleData(outputTrack, buffer, info);
                    }
                    if (!extractor.advance()) break;
                }
                muxer.stop();
                success = true;
            } finally {
                extractor.release();
                if (muxer != null) try { muxer.release(); } catch (RuntimeException ignored) {}
            }
        } finally {
            if (!success) resolver.delete(out, null, null);
        }
        ContentValues publish = new ContentValues(); publish.put(MediaStore.Video.Media.IS_PENDING, 0);
        if (resolver.update(out, publish, null, null) != 1) { resolver.delete(out, null, null); throw new IOException("Publish failed"); }
        return out;
    }
}
