package com.lemenzo.gallery.viewer;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.widget.FrameLayout;
import android.widget.MediaController;

import java.io.IOException;

/**
 * TextureView-based video player used by the full-screen viewer.
 *
 * VideoView/SurfaceView can render audio while leaving a black video surface on
 * some routes/compositors, especially when overlays and backgrounds are present.
 * Keeping video in the normal view hierarchy through TextureView avoids that
 * separate-surface failure mode and gives us deterministic fit-center scaling.
 */
public final class VideoPageView extends FrameLayout
        implements TextureView.SurfaceTextureListener, MediaController.MediaPlayerControl {
    private static final String TAG = "LemenzoGalleryVideo";

    public interface ErrorListener {
        void onError(int what, int extra, Throwable failure);
    }

    private final TextureView texture;
    private MediaPlayer player;
    private Uri source;
    private boolean prepared;
    private boolean playWhenReady = true;
    private int pendingSeekMs;
    private int videoWidth;
    private int videoHeight;
    private ErrorListener errorListener;

    public VideoPageView(Context context) {
        this(context, null);
    }

    public VideoPageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setBackgroundColor(0xff000000);
        setFocusable(true);
        setClickable(true);

        texture = new TextureView(context);
        texture.setSurfaceTextureListener(this);
        addView(texture, new FrameLayout.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    }

    public void setErrorListener(ErrorListener listener) {
        errorListener = listener;
    }

    public void setVideoURI(Uri uri) {
        source = uri;
        pendingSeekMs = 0;
        playWhenReady = true;
        if (texture.isAvailable()) openPlayer(texture.getSurfaceTexture());
    }

    public void release() {
        MediaPlayer old = player;
        player = null;
        prepared = false;
        videoWidth = 0;
        videoHeight = 0;
        if (old != null) {
            try { old.setSurface(null); } catch (RuntimeException ignored) {}
            try { old.reset(); } catch (RuntimeException ignored) {}
            try { old.release(); } catch (RuntimeException ignored) {}
        }
    }

    private void openPlayer(SurfaceTexture surfaceTexture) {
        if (source == null || surfaceTexture == null) return;
        release();

        final MediaPlayer created = new MediaPlayer();
        player = created;
        Surface surface = new Surface(surfaceTexture);
        try {
            created.setAudioAttributes(new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
                    .build());
            created.setDataSource(getContext(), source);
            created.setSurface(surface);
            created.setLooping(false);
            created.setOnVideoSizeChangedListener((mp, width, height) -> {
                videoWidth = width;
                videoHeight = height;
                applyVideoTransform();
            });
            created.setOnPreparedListener(mp -> {
                if (player != mp) return;
                prepared = true;
                videoWidth = Math.max(1, mp.getVideoWidth());
                videoHeight = Math.max(1, mp.getVideoHeight());
                applyVideoTransform();
                if (pendingSeekMs > 0) {
                    try { mp.seekTo(pendingSeekMs); } catch (RuntimeException ignored) {}
                    pendingSeekMs = 0;
                }
                if (playWhenReady) {
                    try { mp.start(); } catch (RuntimeException e) { dispatchError(-1, -1, e); }
                }
            });
            created.setOnErrorListener((mp, what, extra) -> {
                dispatchError(what, extra, null);
                return true;
            });
            created.prepareAsync();
        } catch (IOException | RuntimeException failure) {
            dispatchError(-1, -1, failure);
            release();
        } finally {
            surface.release();
        }
    }

    private void dispatchError(int what, int extra, Throwable failure) {
        Log.e(TAG, "Video playback failed what=" + what + " extra=" + extra, failure);
        if (errorListener != null) errorListener.onError(what, extra, failure);
    }

    private void applyVideoTransform() {
        if (!texture.isAvailable() || videoWidth <= 0 || videoHeight <= 0
                || texture.getWidth() <= 0 || texture.getHeight() <= 0) {
            return;
        }

        float viewWidth = texture.getWidth();
        float viewHeight = texture.getHeight();
        float videoAspect = (float) videoWidth / (float) videoHeight;
        float viewAspect = viewWidth / viewHeight;

        float scaleX = 1f;
        float scaleY = 1f;
        if (videoAspect > viewAspect) {
            float desiredHeight = viewWidth / videoAspect;
            scaleY = desiredHeight / viewHeight;
        } else if (videoAspect < viewAspect) {
            float desiredWidth = viewHeight * videoAspect;
            scaleX = desiredWidth / viewWidth;
        }

        Matrix matrix = new Matrix();
        matrix.setScale(scaleX, scaleY, viewWidth / 2f, viewHeight / 2f);
        texture.setTransform(matrix);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        post(this::applyVideoTransform);
    }

    @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        openPlayer(surface);
    }

    @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        applyVideoTransform();
    }

    @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        release();
        return true;
    }

    @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) {}

    @Override
    protected void onDetachedFromWindow() {
        release();
        super.onDetachedFromWindow();
    }

    public void pauseForLifecycle() {
        boolean wasPlaying = isPlaying();
        if (prepared && player != null) {
            try { player.pause(); } catch (RuntimeException ignored) {}
        }
        playWhenReady = wasPlaying;
    }

    public void resumeForLifecycle() {
        if (playWhenReady && prepared) start();
    }

    @Override public void start() {
        playWhenReady = true;
        if (prepared && player != null) {
            try { player.start(); } catch (RuntimeException e) { dispatchError(-1, -1, e); }
        }
    }

    @Override public void pause() {
        playWhenReady = false;
        if (prepared && player != null) {
            try { player.pause(); } catch (RuntimeException ignored) {}
        }
    }

    @Override public int getDuration() {
        if (!prepared || player == null) return 0;
        try { return player.getDuration(); } catch (RuntimeException e) { return 0; }
    }

    @Override public int getCurrentPosition() {
        if (!prepared || player == null) return pendingSeekMs;
        try { return player.getCurrentPosition(); } catch (RuntimeException e) { return 0; }
    }

    @Override public void seekTo(int pos) {
        int safe = Math.max(0, pos);
        if (!prepared || player == null) { pendingSeekMs = safe; return; }
        try { player.seekTo(safe); } catch (RuntimeException ignored) {}
    }

    @Override public boolean isPlaying() {
        if (!prepared || player == null) return false;
        try { return player.isPlaying(); } catch (RuntimeException e) { return false; }
    }

    @Override public int getBufferPercentage() { return 100; }
    @Override public boolean canPause() { return true; }
    @Override public boolean canSeekBackward() { return true; }
    @Override public boolean canSeekForward() { return true; }
    @Override public int getAudioSessionId() {
        if (player == null) return 0;
        try { return player.getAudioSessionId(); } catch (RuntimeException e) { return 0; }
    }
}
