package com.reactnativevstarcam;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.SurfaceTexture;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.TextView;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Method;

/**
 * Native video view for VStarCam camera streaming.
 * Uses TextureView for video rendering with the native AppPlayer SDK.
 */
public class VStarCamVideoView extends FrameLayout
    implements TextureView.SurfaceTextureListener {

    private static final String TAG = "VStarCamVideoView";
    
    // Set to true for verbose logging during development
    private static final boolean DEBUG_LOGGING = true;
    
    private static void logDebug(String message) {
        if (DEBUG_LOGGING) {
            Log.d(TAG, message);
        }
    }

    private TextureView textureView;
    private TextView statusView;
    private Surface surface;

    // Client info
    private long clientPtr = 0;
    private long sdkClientPtr = 0;  // Actual SDK pointer from VStarCamModule
    private int resolution = 2;

    // Player info
    private long playerPtr = 0;
    private boolean isStreaming = false;
    private boolean playerInitialized = false;
    private boolean pendingStartStream = false;
    private boolean streamingRequested = false;  // Tracks if JS set streaming=true

    // Player class (from AAR)
    private Class<?> appPlayerClass;

    public VStarCamVideoView(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context) {
        setBackgroundColor(Color.BLACK);

        // Create TextureView for video rendering
        textureView = new TextureView(context);
        textureView.setSurfaceTextureListener(this);
        addView(textureView, new LayoutParams(
            LayoutParams.MATCH_PARENT,
            LayoutParams.MATCH_PARENT
        ));

        // Status overlay
        statusView = new TextView(context);
        statusView.setTextColor(Color.WHITE);
        statusView.setTextSize(14);
        statusView.setGravity(Gravity.CENTER);
        statusView.setText("Waiting for connection...");
        LayoutParams statusParams = new LayoutParams(
            LayoutParams.WRAP_CONTENT,
            LayoutParams.WRAP_CONTENT
        );
        statusParams.gravity = Gravity.CENTER;
        addView(statusView, statusParams);

        // Load player library and find class
        loadPlayerLibrary();
    }

    private void loadPlayerLibrary() {
        try {
            // Load the player native lib
            System.loadLibrary("OKSMARTPLAY");
            Log.d(TAG, "OKSMARTPLAY library loaded");

            // Find AppPlayer class - try multiple possible package names
            // Based on decompiling app_player-5.0.0.aar classes.jar
            String[] classNames = {
                "com.veepai.AppPlayerApi",           // Main API class from AAR
                "com.veepai.AppPlayer",              // Added as alternative
                "com.veepai.app_player.AppPlayerPlugin",  // Flutter plugin wrapper
                "com.vstarcam.player.AppPlayer",
                "com.vstarcam.AppPlayer",
                "com.oksmartplay.AppPlayer"
            };

            for (String className : classNames) {
                try {
                    appPlayerClass = Class.forName(className);
                    Log.d(TAG, "Found player class: " + className);
                    
                    // Log available methods for debugging
                    logPlayerMethods();
                    break;
                } catch (ClassNotFoundException ignored) {
                    Log.d(TAG, "Class not found: " + className);
                }
            }

            if (appPlayerClass == null) {
                Log.e(TAG, "Could not find AppPlayer class in AAR");
            }

        } catch (UnsatisfiedLinkError e) {
            Log.e(TAG, "Failed to load OKSMARTPLAY: " + e.getMessage());
        }
    }

    private void logPlayerMethods() {
        if (appPlayerClass == null) return;
        
        Log.d(TAG, "=== AppPlayer Methods ===");
        for (Method m : appPlayerClass.getDeclaredMethods()) {
            StringBuilder params = new StringBuilder();
            for (Class<?> p : m.getParameterTypes()) {
                if (params.length() > 0) params.append(", ");
                params.append(p.getSimpleName());
            }
            Log.d(TAG, "  " + m.getName() + "(" + params + ") -> " +
                m.getReturnType().getSimpleName());
        }
    }

    /**
     * Set the client pointer from VStarCamModule.
     * This is our internal pointer ID, not the SDK pointer.
     */
    public void setClientPtr(long ptr) {
        this.clientPtr = ptr;
        Log.d(TAG, "Client pointer set: " + ptr);

        // Lookup the actual SDK pointer from VStarCamModule
        this.sdkClientPtr = VStarCamModule.getSdkClientPtr((int) ptr);
        Log.d(TAG, "SDK client pointer: " + sdkClientPtr);

        if (sdkClientPtr != 0) {
            // If streaming was already requested (prop order race), start now
            if (streamingRequested && !isStreaming) {
                Log.d(TAG, "clientPtr arrived after streaming requested — starting stream");
                startStream();
            } else {
                statusView.setText("Ready to stream");
            }
        } else {
            statusView.setText("Waiting for connection...");
        }
    }

    /**
     * Set video resolution.
     * 1=high, 2=general, 4=low, 100=superHD
     */
    public void setResolution(int res) {
        this.resolution = res;
        Log.d(TAG, "Resolution set: " + res);
    }

    /**
     * Start video streaming.
     * Uses AppPlayerApi methods discovered via reflection:
     * - createPlayer(long, Surface, int, int, int) -> long
     * - setPlayerSource(long, int, String, String[], long, long[]) -> boolean
     * - start(long) -> boolean
     */
    public void startStream() {
        streamingRequested = true;

        if (sdkClientPtr == 0) {
            this.sdkClientPtr = VStarCamModule.getSdkClientPtr((int) clientPtr);
            if (sdkClientPtr == 0) {
                Log.e(TAG, "Cannot start: no SDK client pointer");
                sendErrorEvent("Cannot start: sdkClientPtr=0, clientPtr=" + clientPtr);
                statusView.setText("Not connected");
                return;
            }
        }

        if (surface == null) {
            Log.d(TAG, "Surface not yet available — deferring stream start");
            sendErrorEvent("Surface null — deferring (pendingStartStream=true)");
            statusView.setText("Preparing...");
            pendingStartStream = true;
            return;
        }

        if (appPlayerClass == null) {
            Log.e(TAG, "Cannot start: player class not found");
            sendErrorEvent("appPlayerClass is null");
            statusView.setText("Player unavailable");
            return;
        }

        statusView.setText("Starting stream...");

        // Start video stream on camera side via CGI
        VStarCamModule.sendCgiInternal((int) clientPtr, 
            String.format("livestream.cgi?streamid=10&substream=%d", resolution), 5);

        try {
            // 1. Create player instance if needed
            if (playerPtr == 0) {
                int width = getWidth() > 0 ? getWidth() : 640;
                int height = getHeight() > 0 ? getHeight() : 480;
                
                Method createPlayerMethod = appPlayerClass.getMethod(
                    "createPlayer", long.class, Surface.class, int.class, int.class, int.class);
                Object result = createPlayerMethod.invoke(null, 0L, surface, width, height, 0);
                playerPtr = (Long) result;
                sendErrorEvent("Player created: ptr=" + playerPtr + " size=" + width + "x" + height);
            }

            // 2. Set the video source (P2P client)
            try {
                Method setSourceMethod = appPlayerClass.getMethod(
                    "setPlayerSource", long.class, int.class, String.class, 
                    String[].class, long.class, long[].class);
                boolean sourceSet = (Boolean) setSourceMethod.invoke(null, 
                    playerPtr, 1, "", new String[0], sdkClientPtr, new long[0]);
                sendErrorEvent("Source set: " + sourceSet + " sdkClient=" + sdkClientPtr);
            } catch (NoSuchMethodException e) {
                sendErrorEvent("setPlayerSource not found: " + e.getMessage());
            }

            // 3. Start playback
            Method startMethod = appPlayerClass.getMethod("start", long.class);
            boolean started = (Boolean) startMethod.invoke(null, playerPtr);
            sendErrorEvent("Playback result: started=" + started);

            isStreaming = true;
            playerInitialized = true;
            statusView.setVisibility(GONE);

            // Send success event to JS
            try {
                com.facebook.react.bridge.WritableMap event = 
                    com.facebook.react.bridge.Arguments.createMap();
                event.putBoolean("streaming", true);
                event.putDouble("playerPtr", playerPtr);
                event.putBoolean("started", started);
                com.facebook.react.uimanager.events.RCTEventEmitter emitter = 
                    ((com.facebook.react.bridge.ReactContext) getContext())
                    .getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter.class);
                emitter.receiveEvent(getId(), "onStreamStarted", event);
            } catch (Exception ignored) {}

        } catch (Exception e) {
            Log.e(TAG, "Failed to start stream", e);
            sendErrorEvent("EXCEPTION: " + e.getClass().getSimpleName() + ": " + e.getMessage());
            statusView.setText("Start failed: " + e.getMessage());
        }
    }

    private void sendErrorEvent(String message) {
        try {
            com.facebook.react.bridge.WritableMap event = 
                com.facebook.react.bridge.Arguments.createMap();
            event.putString("error", message);
            com.facebook.react.uimanager.events.RCTEventEmitter emitter = 
                ((com.facebook.react.bridge.ReactContext) getContext())
                .getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter.class);
            emitter.receiveEvent(getId(), "onStreamError", event);
        } catch (Exception ignored) {}
    }

    /**
     * Stop video streaming.
     */
    public void stopStream() {
        streamingRequested = false;
        if (!isStreaming || playerPtr == 0) return;

        Log.d(TAG, "Stopping stream...");

        // Stop video stream on camera side via CGI
        Log.d(TAG, "Requesting livestream stop from camera...");
        VStarCamModule.sendCgiInternal((int) clientPtr, "livestream.cgi?streamid=11", 5);

        try {
            try {
                Method stopMethod = appPlayerClass.getMethod("stop", long.class);
                stopMethod.invoke(null, playerPtr);
                Log.d(TAG, "Playback stopped");
            } catch (NoSuchMethodException e) {
                // Try pause() instead
                try {
                    Method pauseMethod = appPlayerClass.getMethod("pause", long.class);
                    pauseMethod.invoke(null, playerPtr);
                    Log.d(TAG, "Playback paused");
                } catch (NoSuchMethodException e2) {
                    Log.w(TAG, "No stop/pause method found");
                }
            }

            isStreaming = false;
            statusView.setVisibility(VISIBLE);
            statusView.setText("Stream stopped");

        } catch (Exception e) {
            Log.e(TAG, "Failed to stop stream", e);
        }
    }

    /**
     * Capture current frame and save to file.
     */
    public void captureSnapshot(String path) {
        if (textureView == null) return;
        
        Bitmap bitmap = textureView.getBitmap();
        if (bitmap == null) {
            Log.e(TAG, "Failed to capture snapshot: bitmap is null");
            return;
        }

        try {
            File file = new File(path);
            if (file.exists()) file.delete();
            
            FileOutputStream out = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
            out.flush();
            out.close();
            Log.d(TAG, "Snapshot saved to: " + path);
        } catch (Exception e) {
            Log.e(TAG, "Failed to save snapshot", e);
        }
    }

    private void releasePlayer() {
        if (playerPtr == 0) return;

        try {
            stopStream();

            try {
                Method releaseMethod = appPlayerClass.getMethod("release", long.class);
                releaseMethod.invoke(null, playerPtr);
                Log.d(TAG, "Player released");
            } catch (NoSuchMethodException e) {
                // Try destroy() instead
                try {
                    Method destroyMethod = appPlayerClass.getMethod("destroy", long.class);
                    destroyMethod.invoke(null, playerPtr);
                    Log.d(TAG, "Player destroyed");
                } catch (NoSuchMethodException e2) {
                    Log.w(TAG, "No release/destroy method found");
                }
            }

            playerPtr = 0;

        } catch (Exception e) {
            Log.e(TAG, "Failed to release player", e);
        }
    }

    // TextureView.SurfaceTextureListener

    @Override
    public void onSurfaceTextureAvailable(
            SurfaceTexture surfaceTexture, int width, int height) {
        Log.d(TAG, "Surface available: " + width + "x" + height);
        this.surface = new Surface(surfaceTexture);

        // If startStream() was called before surface was ready, start now
        if (pendingStartStream) {
            Log.d(TAG, "Deferred stream start — surface now available");
            pendingStartStream = false;
            startStream();
            return;
        }

        // If already streaming, update the surface on the existing player
        if (isStreaming && playerPtr != 0 && appPlayerClass != null) {
            try {
                Method setSurfaceMethod = appPlayerClass.getMethod(
                    "setSurface", long.class, Surface.class);
                setSurfaceMethod.invoke(null, playerPtr, surface);
                Log.d(TAG, "Surface updated on existing player");
            } catch (Exception e) {
                Log.e(TAG, "Failed to update surface", e);
            }
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(
            SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "Surface size changed: " + width + "x" + height);
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        Log.d(TAG, "Surface destroyed");
        stopStream();
        if (this.surface != null) {
            this.surface.release();
            this.surface = null;
        }
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        // Frame rendered - nothing to do
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        releasePlayer();
    }
}
