package acquire.sdk;

import android.content.Context;
import android.os.ParcelFileDescriptor;

import com.newland.modules.flyparameterparser.core.handler.ParameterFileHandler;
import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import acquire.base.BaseApplication;
import acquire.base.utils.LoggerUtils;
import acquire.base.utils.file.FileUtils;
import acquire.base.utils.thread.Locker;

/**
 * A tool for Fly Parameter Service.
 *
 * @author Janson
 * @date 2022/1/27 14:58
 */
public class FlyParameterHelper {
    private static volatile FlyParameterHelper instance;
    private Context appContext;
    private boolean updating;
    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    private FlyParameterHelper() {
    }

    public static FlyParameterHelper getInstance() {
        if (instance == null) {
            synchronized (FlyParameterHelper.class) {
                if (instance == null) {
                    instance = new FlyParameterHelper();
                }
            }
        }
        return instance;
    }

    /**
     * Fetch parameters from Custom TMS, bypassing SSL certificate validation.
     * WARNING: This disables SSL verification and is vulnerable to MITM attacks.
     * Use only for debugging or internal networks where the risk is accepted.
     *
     * @param downloadUrl          The URL to download parameters from
     * @param flyParameterCallback download result
     */
    public void fetchParametersIgnoreSsl(String downloadUrl, FlyParameterCallback flyParameterCallback) {
        if (updating) {
            flyParameterCallback.onError(0xFF,
                    BaseApplication.getAppString(R.string.sdk_helper_fly_parameter_updating));
            return;
        }
        updating = true;
        executorService.execute(() -> {
            try {
                LoggerUtils.d("[L3Config] SSL validation DISABLED for this request");
                LoggerUtils.d("[L3Config] Downloading from: " + downloadUrl);

                // Build a trust-all SSLContext scoped to this connection only
                TrustManager[] trustAll = new TrustManager[]{
                    new X509TrustManager() {
                        public void checkClientTrusted(X509Certificate[] chain, String authType) {}
                        public void checkServerTrusted(X509Certificate[] chain, String authType) {}
                        public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
                    }
                };
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, trustAll, new SecureRandom());
                HostnameVerifier allHostsValid = (hostname, session) -> true;

                URL url = new URL(downloadUrl);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                if (connection instanceof HttpsURLConnection) {
                    HttpsURLConnection httpsConn = (HttpsURLConnection) connection;
                    httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
                    httpsConn.setHostnameVerifier(allHostsValid);
                }
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(30000);
                connection.setReadTimeout(30000);

                int responseCode = connection.getResponseCode();
                LoggerUtils.d("[L3Config] HTTP response code: " + responseCode);
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    String contentType = connection.getContentType();
                    LoggerUtils.d("[L3Config] Content-Type: " + contentType);
                    if (contentType != null && contentType.contains("application/json")) {
                        handleJsonResponse(connection.getInputStream(), flyParameterCallback);
                    } else {
                        handleZipResponse(connection.getInputStream(), flyParameterCallback);
                    }
                } else {
                    flyParameterCallback.onError(responseCode, "HTTP Error: " + responseCode);
                }
                connection.disconnect();
            } catch (Exception e) {
                LoggerUtils.e("[L3Config] Error downloading", e);
                flyParameterCallback.onError(0xFF, e.getMessage());
            } finally {
                updating = false;
            }
        });
    }

    /**
     * Fetch parameters from Custom TMS
     *
     * @param downloadUrl          The URL to download parameters from
     * @param flyParameterCallback download result
     */
    public void fetchParameters(String downloadUrl, FlyParameterCallback flyParameterCallback) {
        if (updating) {
            flyParameterCallback.onError(0xFF,
                    BaseApplication.getAppString(R.string.sdk_helper_fly_parameter_updating));
            return;
        }
        updating = true;
        executorService.execute(() -> {
            try {
                LoggerUtils.d("Downloading parameters from: " + downloadUrl);
                URL url = new URL(downloadUrl);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(30000);
                connection.setReadTimeout(30000);

                int responseCode = connection.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    String contentType = connection.getContentType();
                    LoggerUtils.d("Content-Type: " + contentType);

                    if (contentType != null && contentType.contains("application/json")) {
                        // Handle JSON response
                        handleJsonResponse(connection.getInputStream(), flyParameterCallback);
                    } else {
                        // Handle Zip response (default)
                        handleZipResponse(connection.getInputStream(), flyParameterCallback);
                    }
                } else {
                    flyParameterCallback.onError(responseCode, "HTTP Error: " + responseCode);
                }
                connection.disconnect();
            } catch (Exception e) {
                LoggerUtils.e("Error fetching parameters", e);
                flyParameterCallback.onError(0xFF, e.getMessage());
            } finally {
                updating = false;
            }
        });
    }

    private void handleJsonResponse(InputStream inputStream, FlyParameterCallback callback) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        String jsonString = sb.toString();
        JSONObject jsonObject = new JSONObject(jsonString);
        Map<String, Object> map = new HashMap<>();

        // Convert JSONObject to Map (Simplified for now, might need deep conversion)
        // For now, we assume the structure matches what RemoteParamsUpdater expects
        // or we might need to adapt RemoteParamsUpdater to handle JSON directly.
        // Given the existing code uses Map<String, Object>, we'll try to populate it.

        // Note: org.json.JSONObject to Map conversion is needed here.
        // Since we don't have a direct helper, we'll iterate keys.
        java.util.Iterator<String> keys = jsonObject.keys();
        while (keys.hasNext()) {
            String key = keys.next();
            map.put(key, jsonObject.get(key));
        }

        callback.onReceive(map);
    }

    private void handleZipResponse(InputStream inputStream, FlyParameterCallback callback) throws Exception {
        LoggerUtils.d("FlyParameterHelper: Starting handleZipResponse");
        
        String tempDir = BaseApplication.getAppContext().getExternalFilesDir(null).getPath() + File.separator
                + "FlyParameterTemp";
        File tempDirFile = new File(tempDir);
        if (!tempDirFile.exists()) {
            tempDirFile.mkdirs();
        }

        String zipPath = tempDir + File.separator + "FlyParameter.zip";
        File zipFile = new File(zipPath);

        try (FileOutputStream fos = new FileOutputStream(zipFile);
                BufferedInputStream bis = new BufferedInputStream(inputStream)) {
            byte[] buffer = new byte[1024];
            int count;
            long totalBytes = 0;
            while ((count = bis.read(buffer)) != -1) {
                fos.write(buffer, 0, count);
                totalBytes += count;
            }
            LoggerUtils.d("FlyParameterHelper: Downloaded " + totalBytes + " bytes to " + zipPath);
        }

        // DEBUG: List zip entries and read file contents before processing
        try (java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(new FileInputStream(zipFile))) {
            java.util.zip.ZipEntry entry;
            java.util.List<String> entries = new java.util.ArrayList<>();
            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();
                entries.add(entryName + " (size=" + entry.getSize() + ", compressed=" + entry.getCompressedSize() + ")");
                
                // Read first few lines of each file to debug content
                if (entryName.endsWith(".properties")) {
                    try {
                        java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(zis));
                        StringBuilder content = new StringBuilder();
                        String line;
                        int lineCount = 0;
                        while ((line = reader.readLine()) != null && lineCount < 5) {
                            content.append(line).append("\\n");
                            lineCount++;
                        }
                        LoggerUtils.d("FlyParameterHelper: " + entryName + " content (first 5 lines): " + content.toString());
                    } catch (Exception e) {
                        LoggerUtils.e("FlyParameterHelper: failed to read " + entryName, e);
                    }
                }
                zis.closeEntry();
            }
            LoggerUtils.d("FlyParameterHelper: zip entries -> " + entries);
        } catch (Exception ex) {
            LoggerUtils.e("FlyParameterHelper: failed listing zip entries", ex);
        }

        LoggerUtils.d("FlyParameterHelper: Creating ParameterFileHandler with path: " + zipPath);
        
        // Try the original ParameterFileHandler first
        try {
            ParameterFileHandler handler = new ParameterFileHandler(zipPath);
            Map<String, Object> map = handler.getBody(HashMap.class);
            LoggerUtils.d("FlyParameterHelper: ParameterFileHandler returned map with " + (map == null ? 0 : map.size()) + " entries");
            if (map != null && !map.isEmpty()) {
                callback.onReceive(map);
                return;
            }
            LoggerUtils.w("FlyParameterHelper: ParameterFileHandler returned null/empty map, falling back to manual parsing");
        } catch (Exception e) {
            LoggerUtils.e("FlyParameterHelper: ParameterFileHandler failed, trying manual parsing", e);
        }
        
        // Manual parsing fallback using AppParamsImporter logic
        try {
            Map<String, Object> resultMap = new HashMap<>();
            
            try (java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(new FileInputStream(zipFile))) {
                java.util.zip.ZipEntry entry;
                while ((entry = zis.getNextEntry()) != null) {
                    String entryName = entry.getName();
                    LoggerUtils.d("FlyParameterHelper: Processing zip entry: " + entryName);
                    
                    if ("default_params.properties".equals(entryName)) {
                        // Parse parameters using AppParamsImporter logic
                        Map<String, String> params = parsePropertiesFile(zis);
                        resultMap.putAll(params);
                        LoggerUtils.d("FlyParameterHelper: Parsed " + params.size() + " parameters");
                    } else if ("default_merchants.properties".equals(entryName)) {
                        // Parse merchants 
                        java.util.List<Map<String, Object>> merchants = parseMerchantsFile(zis);
                        resultMap.put("Merchants", merchants);
                        LoggerUtils.d("FlyParameterHelper: Parsed " + merchants.size() + " merchants");
                    } else if (entryName.endsWith(".xml") && entryName.toLowerCase().contains("configuration")) {
                        // Handle EMV configuration XML
                        byte[] xmlData = readEntryBytes(zis);
                        java.io.ByteArrayInputStream xmlStream = new java.io.ByteArrayInputStream(xmlData);
                        resultMap.put("Newland_L3_configuration", xmlStream);
                        LoggerUtils.d("FlyParameterHelper: Added EMV configuration: " + entryName);
                    }
                    zis.closeEntry();
                }
            }
            
            LoggerUtils.d("FlyParameterHelper: Manual parsing completed with " + resultMap.size() + " entries");
            callback.onReceive(resultMap);
            
        } catch (Exception manualException) {
            LoggerUtils.e("FlyParameterHelper: Manual parsing also failed", manualException);
            throw manualException;
        }
    }

    private Map<String, String> parsePropertiesFile(java.io.InputStream inputStream) throws java.io.IOException {
        java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));
        Map<String, String> map = new HashMap<>();
        String line;
        String group = "";
        while ((line = reader.readLine()) != null) {
            line = line.trim();
            if (line.matches("\\[.*]")) {
                // e.g. [PARAMS] , group = PARAMS
                group = line.replaceFirst("\\[(.*)]", "$1");
            } else if (line.matches(".*=.*") && !line.startsWith("#")) {
                int i = line.indexOf('=');
                String key = line.substring(0, i).trim();
                String value = line.substring(i + 1).trim();
                if ("PARAMS".equals(group)) {
                    map.put(key, value);
                }
            }
        }
        return map;
    }
    
    private java.util.List<Map<String, Object>> parseMerchantsFile(java.io.InputStream inputStream) throws java.io.IOException {
        java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));
        String line;
        java.util.List<Map<String, Object>> merchantList = new java.util.ArrayList<>();
        Map<String, Object> merchantsMap = new HashMap<>();
        
        String currentOrg = null;
        Map<String, Object> currentMerchant = null;
        
        while ((line = reader.readLine()) != null) {
            line = line.trim();
            if (line.matches("\\[.*]")) {
                // Save previous merchant
                if (currentOrg != null && currentMerchant != null) {
                    java.util.List<Map<String, Object>> orgList = new java.util.ArrayList<>();
                    orgList.add(currentMerchant);
                    merchantsMap.put(currentOrg, orgList);
                }
                
                // Start new merchant
                currentOrg = line.replaceFirst("\\[(.*)]", "$1");
                currentMerchant = new HashMap<>();
            } else if (line.matches(".*=.*") && !line.startsWith("#") && currentMerchant != null) {
                int i = line.indexOf('=');
                String key = line.substring(0, i).trim();
                String value = line.substring(i + 1).trim();
                currentMerchant.put(key, value);
            }
        }
        
        // Save last merchant
        if (currentOrg != null && currentMerchant != null) {
            java.util.List<Map<String, Object>> orgList = new java.util.ArrayList<>();
            orgList.add(currentMerchant);
            merchantsMap.put(currentOrg, orgList);
        }
        
        if (!merchantsMap.isEmpty()) {
            merchantList.add(merchantsMap);
        }
        
        return merchantList;
    }
    
    private byte[] readEntryBytes(java.io.InputStream inputStream) throws java.io.IOException {
        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int count;
        while ((count = inputStream.read(buffer)) != -1) {
            baos.write(buffer, 0, count);
        }
        return baos.toByteArray();
    }

    public interface FlyParameterCallback {
        void onReceive(Map<String, Object> map);

        void onError(int errorCode, String message);
    }
}