package org.torproject.artitoyvpn.ui.home;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.VpnService;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

import org.torproject.artitoyvpn.R;
import org.torproject.artitoyvpn.databinding.FragmentHomeBinding;
import org.torproject.artitoyvpn.vpn.VpnServiceCommand;
import org.torproject.artitoyvpn.vpn.VpnStatusObservable;
import org.torproject.artitoyvpn.vpn.VpnStatusObservable.Status;
import org.torproject.onionmasq.OnionMasq;
import org.torproject.onionmasq.errors.ProxyStoppedException;

public class HomeFragment extends Fragment {

    private HomeViewModel homeViewModel;
    private FragmentHomeBinding binding;

    ActivityResultLauncher<Intent> startForResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        VpnServiceCommand.startVpn(getContext());
                    } else {
                        VpnStatusObservable.update(Status.ERROR);
                    }
                }
            });

    ActivityResultLauncher<String> notificationRequestLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> {
        if (!granted) {
            Toast.makeText(this.getContext(), "You won't see up- and download stats in the notification bar.", Toast.LENGTH_LONG).show();
        }
        handleVpnButton(this.getContext());
    });

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        homeViewModel =
                new ViewModelProvider(this).get(HomeViewModel.class);

        binding = FragmentHomeBinding.inflate(inflater, container, false);
        View root = binding.getRoot();

        final TextView textView = binding.textVpnState;
        homeViewModel.getText().observe(getViewLifecycleOwner(), s -> textView.setText(s));

        OnionMasq.getEventObservable().observe(getViewLifecycleOwner(), event -> homeViewModel.update(event, getContext()));
        final TextView progressTextView = binding.textProgress;;
        homeViewModel.getProgressText().observe(getViewLifecycleOwner(), s -> progressTextView.setText(s));

        VpnStatusObservable.getDataUsage().observe(getViewLifecycleOwner(), dataUsage -> homeViewModel.updateDataUsage(root.getContext(), dataUsage));
        homeViewModel.getOverallThroughputText().observe(getViewLifecycleOwner(), s -> binding.textBandwidth.setText(s));
        homeViewModel.getThroughputText().observe(getViewLifecycleOwner(), s -> binding.textBandwidthPerSec.setText(s));

        final FloatingActionButton refreshCircuitsButton = binding.buttonRefreshCircuits;
        refreshCircuitsButton.setOnClickListener(view -> {
            try {
                OnionMasq.refreshCircuits();
                Toast.makeText(view.getContext(), "Circuits refreshed.", Toast.LENGTH_SHORT).show();
            }
            catch (ProxyStoppedException e) {
                Toast.makeText(view.getContext(), "Error: VPN is not running!", Toast.LENGTH_SHORT).show();
            }
        });

        final Button vpnButton = binding.buttonMain;
        VpnStatusObservable.getStatus().observe(getViewLifecycleOwner(), status -> homeViewModel.update(status, getContext()));
        homeViewModel.getButtonText().observe(getViewLifecycleOwner(), text -> vpnButton.setText(text));
        homeViewModel.isButtonEnabled().observe(getViewLifecycleOwner(), enabled -> vpnButton.setEnabled(enabled));
        vpnButton.setOnClickListener(view -> {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                    homeViewModel.askForNotificationPermission()) {
                    notificationRequestLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
                return;
            }
            handleVpnButton(view.getContext());
        });

        binding.exitNodeCountry.setText(homeViewModel.getExitNodeCountry());
        return root;
    }

    private void handleVpnButton(Context context) {
        Status status = VpnStatusObservable.getStatus().getValue();
        switch (status) {
            case RUNNING:
                VpnStatusObservable.update(Status.STOPPING);
                VpnServiceCommand.stopVpn(context);
                break;
            case STOPPED:
            case ERROR:
                VpnStatusObservable.update(Status.STARTING);
                prepareToStartVPN();
                break;
            default:
                break;

        }
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        homeViewModel.stateText.setValue(getContext().getString(R.string.state_description_off));
        homeViewModel.buttonText.setValue(getContext().getString(R.string.start));
    }

    public void prepareToStartVPN() {
        Intent vpnIntent = null;
        try {
            vpnIntent = VpnService.prepare(getContext().getApplicationContext()); // stops the VPN connection created by another application.
        } catch (NullPointerException npe) {
            VpnStatusObservable.update(VpnStatusObservable.Status.ERROR);
        }
        if (vpnIntent != null) {
            startForResult.launch(vpnIntent);
        } else {
            VpnServiceCommand.startVpn(getContext());
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}