package com.foxx.androidcast.vpn;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;

import com.foxx.androidcast.IVpnOperationCallback;
import com.foxx.androidcast.IVpnService;
import com.foxx.androidcast.IVpnStateCallback;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Main-process client for {@link AndroidCastVpnService} ({@code com.foxx.androidcast:vpn}).
 */
public final class VpnServiceClient {
    private static final String TAG = "VpnServiceClient";
    private static final long BIND_TIMEOUT_MS = 8_000L;

    private static volatile VpnServiceClient instance;

    private final Context appContext;
    private final Handler main = new Handler(Looper.getMainLooper());
    private final Object bindLock = new Object();
    private final List<Runnable> pending = new ArrayList<>();

    private IVpnService service;
    private boolean binding;
    private CountDownLatch bindLatch;

    public interface StateListener {
        void onStateChanged(int state, String detail);
    }

    public interface OperationListener {
        void onSuccess();

        void onFailure(String reason);
    }

    private VpnServiceClient(Context context) {
        appContext = context.getApplicationContext();
    }

    public static VpnServiceClient get(Context context) {
        if (instance == null) {
            synchronized (VpnServiceClient.class) {
                if (instance == null) {
                    instance = new VpnServiceClient(context);
                }
            }
        }
        return instance;
    }

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            synchronized (bindLock) {
                service = IVpnService.Stub.asInterface(binder);
                binding = false;
                if (bindLatch != null) {
                    bindLatch.countDown();
                }
            }
            flushPending();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            synchronized (bindLock) {
                service = null;
                binding = false;
            }
        }
    };

    public boolean isVpnPermissionGranted() {
        if (!ensureBoundSync()) {
            return false;
        }
        try {
            return service.isVpnPermissionGranted();
        } catch (RemoteException e) {
            Log.w(TAG, "isVpnPermissionGranted: " + e.getMessage());
            return false;
        }
    }

    public int getState() {
        if (!ensureBoundSync()) {
            return VpnState.DOWN;
        }
        try {
            return service.getState();
        } catch (RemoteException e) {
            return VpnState.DOWN;
        }
    }

    /** Non-blocking peek for diagnostics — never waits on :vpn process bind. */
    public int getStateIfBound() {
        IVpnService s = service;
        if (s == null) {
            return VpnState.DOWN;
        }
        try {
            return s.getState();
        } catch (RemoteException e) {
            return VpnState.DOWN;
        }
    }

    public String getStateDetailIfBound() {
        IVpnService s = service;
        if (s == null) {
            return "";
        }
        try {
            String detail = s.getStateDetail();
            return detail != null ? detail : "";
        } catch (RemoteException e) {
            return "";
        }
    }

    public boolean isBound() {
        return service != null;
    }

    public String getStateDetail() {
        if (!ensureBoundSync()) {
            return "";
        }
        try {
            String detail = service.getStateDetail();
            return detail != null ? detail : "";
        } catch (RemoteException e) {
            return "";
        }
    }

    public void registerStateListener(StateListener listener) {
        runWithService(s -> {
            IVpnStateCallback cb = new IVpnStateCallback.Stub() {
                @Override
                public void onStateChanged(int state, String detail) {
                    main.post(() -> listener.onStateChanged(state, detail));
                }
            };
            s.registerStateCallback(cb);
        });
    }

    public void applyWireGuardConfig(String wgQuickConfig, OperationListener listener) {
        runWithService(s -> {
            try {
                s.applyWireGuardConfig(wgQuickConfig, wrapOperation(listener));
            } catch (RemoteException e) {
                deliverOperation(listener, false, e.getMessage());
            }
        });
    }

    public void tearDown(OperationListener listener) {
        runWithService(s -> {
            try {
                s.tearDown(wrapOperation(listener));
            } catch (RemoteException e) {
                deliverOperation(listener, false, e.getMessage());
            }
        });
    }

    public void tearDown() {
        tearDown(null);
    }

    private IVpnOperationCallback wrapOperation(OperationListener listener) {
        return new IVpnOperationCallback.Stub() {
            @Override
            public void onSuccess() {
                deliverOperation(listener, true, null);
            }

            @Override
            public void onFailure(String reason) {
                deliverOperation(listener, false, reason);
            }
        };
    }

    private void deliverOperation(OperationListener listener, boolean ok, String reason) {
        if (listener == null) {
            return;
        }
        main.post(() -> {
            if (ok) {
                listener.onSuccess();
            } else {
                listener.onFailure(reason != null ? reason : "error");
            }
        });
    }

    private interface ServiceTask {
        void run(IVpnService s) throws RemoteException;
    }

    private void runWithService(ServiceTask task) {
        IVpnService s = service;
        if (s != null) {
            try {
                task.run(s);
            } catch (RemoteException e) {
                Log.w(TAG, "remote call failed: " + e.getMessage());
            }
            return;
        }
        synchronized (bindLock) {
            pending.add(() -> runWithService(task));
        }
        ensureBoundAsync();
    }

    private void flushPending() {
        List<Runnable> copy;
        synchronized (bindLock) {
            copy = new ArrayList<>(pending);
            pending.clear();
        }
        for (Runnable r : copy) {
            r.run();
        }
    }

    private void ensureBoundAsync() {
        synchronized (bindLock) {
            if (service != null || binding) {
                return;
            }
            binding = true;
            Intent i = new Intent(appContext, AndroidCastVpnService.class);
            appContext.startService(i);
            appContext.bindService(i, connection, Context.BIND_AUTO_CREATE);
        }
    }

    private boolean ensureBoundSync() {
        if (service != null) {
            return true;
        }
        synchronized (bindLock) {
            if (service != null) {
                return true;
            }
            bindLatch = new CountDownLatch(1);
            ensureBoundAsync();
            try {
                if (!bindLatch.await(BIND_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                    Log.w(TAG, "VPN service bind timeout");
                    return false;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            } finally {
                bindLatch = null;
            }
        }
        return service != null;
    }
}
