package com.dragontcp.client;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.net.VpnService;
import android.os.Build;
import android.os.ParcelFileDescriptor;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileDescriptor;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class DragonService extends VpnService {
    public static final String ACTION_CONNECT="com.dragontcp.client.CONNECT";
    public static final String ACTION_STOP="com.dragontcp.client.STOP";
    public static volatile boolean active=false,running=false;
    public static volatile String state="Stopped";
    private static final String CHANNEL_ID="dragontcp_vpn";
    private static final int NOTIFICATION_ID=53;
    private final Object lifecycleLock=new Object();
    private Process process;
    private Thread outputThread;
    private ParcelFileDescriptor vpnInterface;
    private File fdSocketFile;

    @Override public void onCreate(){super.onCreate();createNotificationChannel();}
    @Override public int onStartCommand(Intent intent,int flags,int startId){
        if(intent==null)return START_NOT_STICKY;String action=intent.getAction();
        if(ACTION_STOP.equals(action)){appendLog("STOP requested");shutdown("Stopped by user",true);return START_NOT_STICKY;}
        if(!ACTION_CONNECT.equals(action))return START_NOT_STICKY;
        cleanupResources(true);clearLog();active=true;running=false;state="Starting VPN";startForeground(NOTIFICATION_ID,buildNotification("Starting full VPN"));
        String server=intent.getStringExtra("server"),token=intent.getStringExtra("token"),timeout=intent.getStringExtra("timeout"),v4=intent.getStringExtra("vpnIPv4"),v6=intent.getStringExtra("vpnIPv6");
        int port=intent.getIntExtra("port",53),max=intent.getIntExtra("chunkMax",1048576),min=intent.getIntExtra("chunkMin",32),start=intent.getIntExtra("chunkStart",max);
        if(server==null||server.trim().isEmpty()){failStart("Server is empty");return START_NOT_STICKY;}if(token==null)token="";if(timeout==null||timeout.isEmpty())timeout="2s";if(v4==null||v6==null){failStart("Missing VPN client address");return START_NOT_STICKY;}
        start=max;
        try{
            establishPacketVpn(v4,v6);
            state="Starting DragonTCP core";
            startCore(server.trim(),port,token,start,min,max,timeout.trim(),v4,v6);
            state="Connecting to DragonTCP server";
            updateNotification("Connecting • TCP/"+port);
        }catch(Exception e){failStart(e.getMessage()==null?e.toString():e.getMessage());}
        return START_NOT_STICKY;
    }

    private void establishPacketVpn(String v4,String v6)throws Exception{
        VpnService.Builder b=new VpnService.Builder();b.setSession("DragonTCP VPN");b.setMtu(1280);
        b.addAddress(v4,32);b.addAddress(v6,128);b.addRoute("0.0.0.0",0);b.addRoute("::",0);
        b.addDnsServer("1.1.1.1");b.addDnsServer("2606:4700:4700::1111");
        try{b.addDisallowedApplication(getPackageName());}catch(PackageManager.NameNotFoundException e){throw new Exception("Cannot exclude DragonTCP from its own VPN",e);}
        Intent open=new Intent(this,MainActivity.class);open.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP|Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pi=PendingIntent.getActivity(this,1,open,PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE);b.setConfigureIntent(pi);
        vpnInterface=b.establish();if(vpnInterface==null)throw new Exception("Android did not establish the TUN interface");
        appendLog("TUN established: "+v4+" + "+v6+" MTU=1280");appendLog("Routes captured: 0.0.0.0/0 and ::/0");appendLog("DNS through VPN: 1.1.1.1 + 2606:4700:4700::1111");appendLog("DragonTCP app UID excluded from VPN to prevent recursion");
    }

    private void startCore(String server,int port,String token,int start,int min,int max,String timeout,String v4,String v6)throws Exception{
        String executable=getApplicationInfo().nativeLibraryDir+"/libdragontcp_vpn.so";File exe=new File(executable);if(!exe.exists())throw new Exception("Embedded DragonTCP VPN core was not extracted");
        fdSocketFile=new File(getFilesDir(),"dragontcp-tunfd.sock");if(fdSocketFile.exists())fdSocketFile.delete();
        List<String> cmd=new ArrayList<String>();cmd.add(executable);cmd.add("--server-host");cmd.add(server);cmd.add("--server-port");cmd.add(String.valueOf(port));cmd.add("--token");cmd.add(token);
        cmd.add("--tun-fd-socket");cmd.add(fdSocketFile.getAbsolutePath());cmd.add("--vpn-ipv4");cmd.add(v4);cmd.add("--vpn-ipv6");cmd.add(v6);cmd.add("--vpn-mtu");cmd.add("1280");
        cmd.add("--chunk-start");cmd.add(String.valueOf(max));cmd.add("--chunk-max");cmd.add(String.valueOf(max));cmd.add("--chunk-min");cmd.add(String.valueOf(min));cmd.add("--chunk-grow-after");cmd.add("64");cmd.add("--chunk-timeout");cmd.add(timeout);cmd.add("--chunk-reconnect-every");cmd.add("32");cmd.add("--chunk-adapt-log");
        appendLog("Server: "+server+":"+port);appendLog("Transport chunks: start=max="+max+" min="+min+" pollers=1 timeout="+timeout);
        ProcessBuilder pb=new ProcessBuilder(cmd);pb.redirectErrorStream(true);pb.directory(getFilesDir());final Process p=pb.start();synchronized(lifecycleLock){process=p;}
        outputThread=new Thread(()->readCoreOutput(p),"DragonTCP-output");outputThread.setDaemon(true);outputThread.start();
        passTunFdWhenReady();
    }

    private void passTunFdWhenReady()throws Exception{
        long deadline=System.currentTimeMillis()+5000;while(System.currentTimeMillis()<deadline){if(fdSocketFile!=null&&fdSocketFile.exists())break;Process p; synchronized(lifecycleLock){p=process;}if(p==null||!p.isAlive())throw new Exception("DragonTCP core exited before TUN handoff");Thread.sleep(25);}
        if(fdSocketFile==null||!fdSocketFile.exists())throw new Exception("DragonTCP core did not create its TUN-fd socket");
        LocalSocket s=new LocalSocket();try{s.connect(new LocalSocketAddress(fdSocketFile.getAbsolutePath(),LocalSocketAddress.Namespace.FILESYSTEM));FileDescriptor fd=vpnInterface.getFileDescriptor();s.setFileDescriptorsForSend(new FileDescriptor[]{fd});s.getOutputStream().write(0x44);s.getOutputStream().flush();appendLog("TUN file descriptor passed to DragonTCP core");}finally{try{s.close();}catch(Exception ignored){}}
    }

    private void readCoreOutput(Process p){
        try{BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));String line;while((line=br.readLine())!=null){appendLog(line);if(line.contains("VPN READY")){running=true;active=true;state="Connected • Full VPN";updateNotification("Connected • IPv4 + IPv6 • TCP/UDP");}}
            int code=p.waitFor();handleCoreExit(p,code);
        }catch(Exception e){appendLog("Core reader: "+e);handleCoreExit(p,-1);}
    }
    private void handleCoreExit(Process p,int code){boolean owns; synchronized(lifecycleLock){owns=process==p;if(owns)process=null;}if(!owns)return;appendLog("DragonTCP core exited: "+code);running=false;active=false;state="Core exited ("+code+")";closeVpn();stopForeground(true);stopSelf();}

    private Notification buildNotification(String msg){Intent open=new Intent(this,MainActivity.class);open.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP|Intent.FLAG_ACTIVITY_CLEAR_TOP);PendingIntent op=PendingIntent.getActivity(this,0,open,PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE);Intent stop=new Intent(this,DragonService.class);stop.setAction(ACTION_STOP);PendingIntent sp=PendingIntent.getService(this,2,stop,PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE);Notification.Builder nb=Build.VERSION.SDK_INT>=26?new Notification.Builder(this,CHANNEL_ID):new Notification.Builder(this);return nb.setContentTitle("DragonTCP VPN").setContentText(msg).setSmallIcon(android.R.drawable.stat_sys_upload).setOngoing(true).setContentIntent(op).addAction(android.R.drawable.ic_menu_close_clear_cancel,"STOP",sp).build();}
    private void updateNotification(String m){NotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);if(nm!=null)nm.notify(NOTIFICATION_ID,buildNotification(m));}
    private void createNotificationChannel(){if(Build.VERSION.SDK_INT>=26){NotificationChannel c=new NotificationChannel(CHANNEL_ID,"DragonTCP VPN",NotificationManager.IMPORTANCE_LOW);c.setDescription("DragonTCP full packet VPN status");NotificationManager nm=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);if(nm!=null)nm.createNotificationChannel(c);}}
    private synchronized void appendLog(String line){try(PrintWriter out=new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(getFilesDir(),"dragontcp.log"),true),"UTF-8"))){out.println(line);out.flush();}catch(Exception ignored){}}
    private void clearLog(){try{new FileOutputStream(new File(getFilesDir(),"dragontcp.log"),false).close();}catch(Exception ignored){}}
    private void failStart(String m){appendLog("START ERROR: "+m);running=false;active=false;state="Start failed: "+m;cleanupResources(true);stopForeground(true);stopSelf();}
    private void shutdown(String reason,boolean stop){state="Stopping";running=false;appendLog(reason);cleanupResources(true);active=false;state="Stopped";stopForeground(true);if(stop)stopSelf();}
    private void cleanupResources(boolean kill){Process p; synchronized(lifecycleLock){p=process;process=null;}if(p!=null){try{p.getInputStream().close();}catch(Exception ignored){}try{p.destroy();}catch(Exception ignored){}if(kill){try{if(!p.waitFor(800,TimeUnit.MILLISECONDS)){p.destroyForcibly();p.waitFor(800,TimeUnit.MILLISECONDS);}}catch(Exception ignored){try{p.destroyForcibly();}catch(Exception ignored2){}}}}Thread t=outputThread;outputThread=null;if(t!=null&&t!=Thread.currentThread())t.interrupt();closeVpn();if(fdSocketFile!=null){fdSocketFile.delete();fdSocketFile=null;}running=false;}
    private void closeVpn(){ParcelFileDescriptor v=vpnInterface;vpnInterface=null;if(v!=null){try{v.close();}catch(Exception ignored){}}}
    @Override public void onRevoke(){appendLog("VPN permission revoked");shutdown("VPN revoked",true);super.onRevoke();}
    @Override public void onDestroy(){cleanupResources(true);active=false;running=false;if(!state.startsWith("Start failed")&&!state.startsWith("Core exited"))state="Stopped";stopForeground(true);super.onDestroy();}
}
