package com.amazon.ads.apidemo;

import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.viewpager2.widget.ViewPager2;

import com.amazon.ads.apidemo.adapters.TabAdapter;
import com.amazon.ads.apidemo.api.RemoteApiClient;
import com.amazon.ads.apidemo.utils.L;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;

import java.io.File;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
 * Main activity for Partner API Demo application.
 * Demonstrates integration with Teton Remote Management API.
 */
public class MainActivity extends AppCompatActivity {
    // Permission request code
    private static final int REQUEST_DEVICE_MANAGEMENT_PERMISSION = 1001;
    private static final String DEVICE_MANAGEMENT_PERMISSION = "com.amazon.ads.ema.permission.DEVICE_MANAGEMENT";

    // Health monitoring configuration
    private static final long HEALTH_CHECK_INTERVAL_MS = 30000; // 30 seconds
    private static final long INITIAL_RECONNECT_DELAY_MS = 2000; // 2 seconds
    private static final long MAX_RECONNECT_DELAY_MS = 60000; // 60 seconds
    private static final int MAX_RECONNECT_ATTEMPTS = 10;
    // Back press to exit configuration
    private static final long BACK_PRESS_INTERVAL = 3000; // 3 seconds
    private long mLastBackPressTime = 0;
    public static boolean isForeground = false;
    private PowerManager.WakeLock mWakeLock;
    private TextView mTvConnectionStatus;
    private TextView mTvLastApiCall;
    private TextView mTvClock;
    private TextView mTvAppVersion;
    private TextView mTvBuildDate;
    private TabLayout mTabLayout;
    private ViewPager2 mViewPager;
    private Handler mClockHandler;
    private Runnable mClockRunnable;
    private RemoteApiClient mApiClient;
    private TabAdapter mTabAdapter;
    // Health monitoring and reconnection
    private Handler mHealthMonitorHandler;
    private Runnable mHealthCheckRunnable;
    private Handler mReconnectHandler;
    private Runnable mReconnectRunnable;
    private int mReconnectAttempts = 0;
    private boolean mIsReconnecting = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        L.w("onCreate");

        // Keep screen on
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        setContentView(R.layout.activity_main);

        // Initialize UI components
        mTvConnectionStatus = findViewById(R.id.tv_connection_status);
        mTvLastApiCall = findViewById(R.id.tv_last_api_call);
        mTvClock = findViewById(R.id.tv_clock);
        mTvAppVersion = findViewById(R.id.tv_app_version);
        mTvBuildDate = findViewById(R.id.tv_build_date);
        mTabLayout = findViewById(R.id.tab_layout);
        mViewPager = findViewById(R.id.view_pager);

        // Set app version and build date
        setAppVersionInfo();

        // Initialize API client
        mApiClient = new RemoteApiClient(this);

        // Setup tabs
        setupTabs();

        // Check and request permission before connecting to API
        checkAndRequestPermission();

        // Initialize and start time clock
        initializeClock();
    }

    /**
     * Check if DEVICE_MANAGEMENT permission is granted and request if needed
     */
    private void checkAndRequestPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, DEVICE_MANAGEMENT_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
                L.i("DEVICE_MANAGEMENT permission not granted, requesting...");

                // Check if we should show an explanation
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, DEVICE_MANAGEMENT_PERMISSION)) {
                    // Show explanation dialog before requesting permission
                    showPermissionExplanationDialog();
                } else {
                    // Directly request permission
                    requestDeviceManagementPermission();
                }
            } else {
                L.i("DEVICE_MANAGEMENT permission already granted");
                // Permission already granted, connect to API
                connectToPartnerApi();
            }
        } else {
            // For API level < 23, permission is granted at install time
            L.i("API level < 23, no runtime permission needed");
            connectToPartnerApi();
        }
    }

    /**
     * Show explanation dialog for why the permission is needed
     */
    private void showPermissionExplanationDialog() {
        new AlertDialog.Builder(this).setTitle("Permission Required").setMessage(
                        "This app requires Device Management permission to access EMA Plus Partner API. " +
                                "This permission is necessary for device control and monitoring features.")
                .setPositiveButton("Grant Permission", (dialog, which) -> {
                    requestDeviceManagementPermission();
                }).setNegativeButton("Cancel", (dialog, which) -> {
                    L.w("User declined permission explanation");
                    Toast.makeText(this, "Permission denied. Some features may not work.", Toast.LENGTH_LONG).show();
                    updateConnectionStatus("Permission Required", false);
                }).setCancelable(false).show();
    }

    /**
     * Request DEVICE_MANAGEMENT permission
     */
    private void requestDeviceManagementPermission() {
        L.i("Requesting DEVICE_MANAGEMENT permission");
        ActivityCompat.requestPermissions(this, new String[]{DEVICE_MANAGEMENT_PERMISSION}, REQUEST_DEVICE_MANAGEMENT_PERMISSION);
    }

    /**
     * Handle permission request result
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQUEST_DEVICE_MANAGEMENT_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                L.i("DEVICE_MANAGEMENT permission granted");
                Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show();
                // Permission granted, connect to API
                connectToPartnerApi();
            } else {
                L.w("DEVICE_MANAGEMENT permission denied");
                Toast.makeText(this, "Permission denied. App functionality may be limited.", Toast.LENGTH_LONG).show();
                updateConnectionStatus("Permission Denied", false);
                updateLastApiCall("Permission required to connect to Partner API");

                // Check if user checked "Don't ask again"
                if (!ActivityCompat.shouldShowRequestPermissionRationale(this, DEVICE_MANAGEMENT_PERMISSION)) {
                    L.w("Permission denied with 'Don't ask again'");
                    showPermissionDeniedDialog();
                }
            }
        }
    }

    /**
     * Show dialog when permission is permanently denied
     */
    private void showPermissionDeniedDialog() {
        new AlertDialog.Builder(this).setTitle("Permission Required")
                .setMessage("Device Management permission is required for this app to function properly. " + "Please enable it in app settings.")
                .setPositiveButton("Open Settings", (dialog, which) -> {
                    // Open app settings
                    android.content.Intent intent = new android.content.Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                    android.net.Uri uri = android.net.Uri.fromParts("package", getPackageName(), null);
                    intent.setData(uri);
                    startActivity(intent);
                }).setNegativeButton("Cancel", (dialog, which) -> {
                    L.w("User declined to open app settings");
                }).show();
    }

    /**
     * Set app version and build date information
     */
    private void setAppVersionInfo() {
        try {
            // Get package info
            PackageManager pm = getPackageManager();
            PackageInfo pInfo = pm.getPackageInfo(getPackageName(), 0);

            // Set version string: v{versionName}-{versionCode}
            String versionString = "v" + pInfo.versionName + "-" + pInfo.versionCode;
            if (mTvAppVersion != null) {
                mTvAppVersion.setText(versionString);
            }

            // Try to get build date from APK file
            ApplicationInfo appInfo = pm.getApplicationInfo(getPackageName(), 0);
            String apkPath = appInfo.sourceDir;
            File apkFile = new File(apkPath);

            if (apkFile.exists()) {
                long lastModified = apkFile.lastModified();
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
                String buildDate = "Built at: " + dateFormat.format(new Date(lastModified));

                if (mTvBuildDate != null) {
                    mTvBuildDate.setText(buildDate);
                    mTvBuildDate.setVisibility(android.view.View.VISIBLE);
                }

                L.i("App version: " + versionString + ", " + buildDate);
            } else {
                L.i("App version: " + versionString);
            }
        } catch (Exception e) {
            L.e("Error setting app version info:", e);
            // Set default version if error occurs
            if (mTvAppVersion != null) {
                mTvAppVersion.setText("v" + BuildConfig.VERSION_NAME + "-" + BuildConfig.VERSION_CODE);
            }
        }
    }

    private void setupTabs() {
        mTabAdapter = new TabAdapter(this);
        mViewPager.setAdapter(mTabAdapter);

        // Link TabLayout with ViewPager2
        new TabLayoutMediator(mTabLayout, mViewPager, (tab, position) -> tab.setText(mTabAdapter.getTabTitle(position))).attach();
    }

    private void connectToPartnerApi() {
        L.i("Connecting to Partner API service...");
        updateConnectionStatus("Connecting...", false);

        mApiClient.connect(new RemoteApiClient.ConnectionListener() {
            @Override
            public void onConnected() {
                L.i("Connected to Partner API service");
                runOnUiThread(() -> {
                    // Reset reconnection attempts
                    mReconnectAttempts = 0;
                    mIsReconnecting = false;

                    updateConnectionStatus("Connected", true);
                    updateLastApiCall("Connected to Partner API");
                    Toast.makeText(MainActivity.this, "Connected to Partner API", Toast.LENGTH_SHORT).show();

                    // Start health monitoring
                    startHealthMonitoring();
                });
            }

            @Override
            public void onDisconnected() {
                L.w("Disconnected from Partner API service");
                runOnUiThread(() -> {
                    updateConnectionStatus("Disconnected", false);
                    updateLastApiCall("Disconnected from Partner API");
                    Toast.makeText(MainActivity.this, "Disconnected from Partner API", Toast.LENGTH_SHORT).show();

                    // Stop health monitoring
                    stopHealthMonitoring();

                    // Attempt reconnection
                    attemptReconnection();
                });
            }

            @Override
            public void onConnectionFailed(String error) {
                L.e("Failed to connect to Partner API: " + error);
                runOnUiThread(() -> {
                    updateConnectionStatus("Failed", false);
                    updateLastApiCall("Connection failed: " + error);
                    Toast.makeText(MainActivity.this, "Failed to connect: " + error, Toast.LENGTH_LONG).show();

                    // Stop health monitoring
                    stopHealthMonitoring();

                    // Attempt reconnection
                    attemptReconnection();
                });
            }
        });
    }

    /**
     * Start periodic health monitoring of the Partner API connection
     */
    private void startHealthMonitoring() {
        L.i("Starting health monitoring for Partner API connection");

        // Stop any existing monitoring
        stopHealthMonitoring();

        mHealthMonitorHandler = new Handler(Looper.getMainLooper());
        mHealthCheckRunnable = new Runnable() {
            @Override
            public void run() {
                performHealthCheck();
                // Schedule next check
                if (mHealthMonitorHandler != null) {
                    mHealthMonitorHandler.postDelayed(this, HEALTH_CHECK_INTERVAL_MS);
                }
            }
        };

        // Start first health check after initial delay
        mHealthMonitorHandler.postDelayed(mHealthCheckRunnable, HEALTH_CHECK_INTERVAL_MS);
    }

    /**
     * Stop health monitoring
     */
    private void stopHealthMonitoring() {
        if (mHealthMonitorHandler != null && mHealthCheckRunnable != null) {
            L.i("Stopping health monitoring");
            mHealthMonitorHandler.removeCallbacks(mHealthCheckRunnable);
            mHealthMonitorHandler = null;
            mHealthCheckRunnable = null;
        }
    }

    /**
     * Perform a health check on the Partner API connection
     */
    private void performHealthCheck() {
        if (mApiClient == null) {
            return;
        }

        // Run health check in background thread
        new Thread(() -> {
            boolean isHealthy = mApiClient.checkConnectionHealth();

            runOnUiThread(() -> {
                if (!isHealthy) {
                    L.w("Health check failed - connection unhealthy");
                    updateLastApiCall("Health check failed - reconnecting...");

                    // Disconnect and attempt reconnection
                    mApiClient.disconnect();
                    stopHealthMonitoring();
                    attemptReconnection();
                } else {
                    L.i("Health check passed - connection healthy");
                }
            });
        }).start();
    }

    /**
     * Attempt to reconnect to Partner API with exponential backoff
     */
    private void attemptReconnection() {
        // Don't start multiple reconnection attempts
        if (mIsReconnecting) {
            L.w("Reconnection already in progress");
            return;
        }

        // Check if max attempts reached
        if (mReconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
            L.e("Max reconnection attempts reached (" + MAX_RECONNECT_ATTEMPTS + ")");
            updateConnectionStatus("Failed - Max retries", false);
            updateLastApiCall("Max reconnection attempts reached");
            Toast.makeText(this, "Unable to reconnect to Partner API", Toast.LENGTH_LONG).show();
            return;
        }

        mIsReconnecting = true;
        mReconnectAttempts++;

        // Calculate delay with exponential backoff: delay = min(initial * 2^attempts, max)
        long delay = Math.min(INITIAL_RECONNECT_DELAY_MS * (long) Math.pow(2, mReconnectAttempts - 1), MAX_RECONNECT_DELAY_MS);

        L.i("Scheduling reconnection attempt " + mReconnectAttempts + " in " + delay + "ms");
        updateConnectionStatus("Reconnecting (" + mReconnectAttempts + "/" + MAX_RECONNECT_ATTEMPTS + ")...", false);
        updateLastApiCall("Reconnecting in " + (delay / 1000) + "s (attempt " + mReconnectAttempts + ")");

        // Cancel any pending reconnection
        if (mReconnectHandler != null && mReconnectRunnable != null) {
            mReconnectHandler.removeCallbacks(mReconnectRunnable);
        }

        mReconnectHandler = new Handler(Looper.getMainLooper());
        mReconnectRunnable = () -> {
            L.i("Executing reconnection attempt " + mReconnectAttempts);
            mIsReconnecting = false;
            connectToPartnerApi();
        };

        mReconnectHandler.postDelayed(mReconnectRunnable, delay);
    }

    /**
     * Cancel any pending reconnection attempts
     */
    private void cancelReconnection() {
        if (mReconnectHandler != null && mReconnectRunnable != null) {
            L.i("Cancelling pending reconnection");
            mReconnectHandler.removeCallbacks(mReconnectRunnable);
            mReconnectHandler = null;
            mReconnectRunnable = null;
        }
        mIsReconnecting = false;
    }

    private void updateConnectionStatus(String status, boolean isConnected) {
        mTvConnectionStatus.setText(status);
        if (isConnected) {
            mTvConnectionStatus.setTextColor(getColor(android.R.color.holo_green_light));
        } else {
            mTvConnectionStatus.setTextColor(getColor(android.R.color.holo_red_light));
        }
    }

    /**
     * Update the last API call status - can be called from fragments
     */
    public void updateLastApiCall(String message) {
        runOnUiThread(() -> {
            if (mTvLastApiCall != null) {
                mTvLastApiCall.setText(message);
            }
        });
    }

    /**
     * Get the API client instance - used by fragments
     */
    public RemoteApiClient getApiClient() {
        return mApiClient;
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Keep screen on
        PowerManager pManager = ((PowerManager) getSystemService(POWER_SERVICE));
        mWakeLock = pManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, MainActivity.class.getSimpleName());
        mWakeLock.acquire(10 * 60 * 1000L /*10 minutes*/);

        isForeground = true;

        // Resume clock
        if (mClockHandler != null && mClockRunnable != null) {
            mClockHandler.post(mClockRunnable);
        }

        // Resume health monitoring if connected
        if (mApiClient != null && mApiClient.isConnected()) {
            startHealthMonitoring();
        }

        View rootView = getWindow().getDecorView().getRootView();
        rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int width = rootView.getWidth();
                int height = rootView.getHeight();

                L.i("Root view size:", width, height);

                rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }

    @Override
    protected void onPause() {
        super.onPause();

        isForeground = false;

        // Pause clock
        if (mClockHandler != null && mClockRunnable != null) {
            mClockHandler.removeCallbacks(mClockRunnable);
        }

        // Pause health monitoring (save battery when app is in background)
        stopHealthMonitoring();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mWakeLock != null && mWakeLock.isHeld()) {
            mWakeLock.release();
        }
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        L.d("MainActivity dispatchKeyEvent - keyCode: " + event.getKeyCode() + ", action: " + event.getAction() + ", KeyEvent: " +
                KeyEvent.keyCodeToString(event.getKeyCode()));

        // Handle tab navigation with arrow keys
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            int keyCode = event.getKeyCode();
            switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_LEFT:
                // Navigate to previous tab
                L.i("MainActivity handling LEFT arrow - navigate to previous tab");
                if (mViewPager != null) {
                    final int idx = mViewPager.getCurrentItem() > 0 ? mViewPager.getCurrentItem() - 1 : mTabAdapter.getItemCount() - 1;
                    mViewPager.setCurrentItem(idx);
                    return true;
                }
                break;

            case KeyEvent.KEYCODE_DPAD_RIGHT:
                // Navigate to next tab
                L.i("MainActivity handling RIGHT arrow - navigate to next tab");
                if (mViewPager != null && mTabAdapter != null) {
                    final int idx = mViewPager.getCurrentItem() < mTabAdapter.getItemCount() - 1 ? mViewPager.getCurrentItem() + 1 : 0;
                    mViewPager.setCurrentItem(idx);
                    return true;
                }
                break;
            }
        }
        // Let the default dispatch handle other keys
        L.d("MainActivity dispatching key to super (not handled by MainActivity)");
        boolean result = super.dispatchKeyEvent(event);
        L.d("MainActivity dispatchKeyEvent result from super: " + result);
        return result;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // Stop health monitoring
        stopHealthMonitoring();

        // Cancel any pending reconnection
        cancelReconnection();

        // Disconnect from API service
        if (mApiClient != null) {
            mApiClient.disconnect();
        }

        // Stop clock
        if (mClockHandler != null && mClockRunnable != null) {
            mClockHandler.removeCallbacks(mClockRunnable);
        }
    }

    /**
     * Initialize and start the time clock display
     */
    private void initializeClock() {
        mClockHandler = new Handler(Looper.getMainLooper());

        mClockRunnable = new Runnable() {
            @Override
            public void run() {
                updateClock();
                // Update every second
                mClockHandler.postDelayed(this, 1000);
            }
        };

        // Start the clock immediately
        mClockHandler.post(mClockRunnable);
        L.i("Time clock initialized and started");
    }

    /**
     * Update the clock display with current time in HH:MM:SS format
     */
    private void updateClock() {
        try {
            SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
            String currentTime = timeFormat.format(new Date());
            String timezone = getDescriptiveTimeZone();
            String displayText = currentTime + " " + timezone;

            if (mTvClock != null) {
                mTvClock.setText(displayText);
            }
        } catch (Exception e) {
            L.e("Error updating clock:", e);
        }
    }

    /**
     * Get a descriptive string for the current timezone.
     *
     * @return A string like "Asia/Shanghai-CST(GMT+8)".
     */
    public static String getDescriptiveTimeZone() {
        final ZoneId currentZone = ZoneId.systemDefault();
        final TimeZone timeZone = TimeZone.getDefault();
        // Get offset in hours
        final int rawOffset = timeZone.getRawOffset() / (60 * 60 * 1000);
        final String shortName = timeZone.getDisplayName(false, TimeZone.SHORT);
        if (shortName.contains("GMT")) {
            return String.format("%s-%s", currentZone, shortName);
        } else {
            final String offsetString = String.format(rawOffset >= 0 ? "GMT+%d" : "GMT%d", rawOffset);
            return String.format("%s-%s(%s)", currentZone, shortName, offsetString);
        }
    }

    /**
     * Handle back button press - requires double press within 3 seconds to exit
     */
    @Override
    public void onBackPressed() {
        long currentTime = System.currentTimeMillis();

        // Check if this is the second press within the time interval
        if (currentTime - mLastBackPressTime < BACK_PRESS_INTERVAL) {
            // Second press within 3 seconds - exit the app
            L.i("Back pressed twice within 3 seconds - exiting app");
            super.onBackPressed();
            finish();
        } else {
            // First press or press after timeout - show toast and record time
            L.i("Back pressed - showing exit prompt");
            mLastBackPressTime = currentTime;
            Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT).show();
        }
    }
}
