package ir.gemacc.admin;

import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class SmsUploader {
    public static final String PREFS = "gemacc_sms_sync";
    public static final int JOB_IMMEDIATE = 384221;
    public static final int JOB_PERIODIC_SCAN = 384222;

    private static final String KEY_QUEUE = "sms_queue_v3";
    private static final String KEY_LAST_RECEIVED_AT = "last_received_at";
    private static final String KEY_LAST_RECEIVED_SENDER = "last_received_sender";
    private static final String KEY_LAST_SUCCESS_AT = "last_success_at";
    private static final String KEY_LAST_SCAN_AT = "last_scan_at";
    private static final String KEY_LAST_SERVER_STATUS = "last_server_status";
    private static final String KEY_LAST_HEARTBEAT_AT = "last_heartbeat_at";
    private static final String KEY_LAST_HEARTBEAT_MESSAGE = "last_heartbeat_message";
    private static final String KEY_LAST_ERROR = "last_error";
    private static final String KEY_TOTAL_SENT = "total_sent";
    private static final int MAX_QUEUE_SIZE = 1000;
    private static final long PERIODIC_INTERVAL_MS = 15L * 60L * 1000L;

    private SmsUploader() {}

    public interface ScanCallback { void onDone(int scanned, int queued, int sent); }
    public interface TestCallback { void onDone(boolean ok, String message); }

    public static void initialize(Context context) {
        Context app = context.getApplicationContext();
        schedulePeriodicSafetyScan(app);
        scheduleImmediate(app);
        if (hasReadSmsPermission(app)) {
            scanRecentSmsAsync(app, 72, null);
        }
        testServerAsync(app, null);
    }

    public static boolean hasReadSmsPermission(Context context) {
        return Build.VERSION.SDK_INT < 23 ||
                context.checkSelfPermission(android.Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED;
    }

    public static boolean hasReceiveSmsPermission(Context context) {
        return Build.VERSION.SDK_INT < 23 ||
                context.checkSelfPermission(android.Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_GRANTED;
    }

    private static String baseUrl() {
        String u = BuildConfig.SMS_BASE_URL == null ? "" : BuildConfig.SMS_BASE_URL.trim();
        if (!u.startsWith("http://") && !u.startsWith("https://")) u = "https://" + u;
        while (u.endsWith("/")) u = u.substring(0, u.length() - 1);
        if (u.endsWith("/index.html")) u = u.substring(0, u.length() - "/index.html".length());
        if (!u.contains("/mini_app")) u += "/mini_app";
        return u;
    }

    public static String configuredHost() {
        try { return Uri.parse(baseUrl()).getHost(); } catch (Exception ignored) { return baseUrl(); }
    }

    public static boolean isConfigured() {
        return baseUrl().startsWith("https://") && BuildConfig.SMS_APP_TOKEN != null && BuildConfig.SMS_APP_TOKEN.length() >= 24;
    }

    public static void noteIncomingSms(Context context, String sender) {
        context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
                .putLong(KEY_LAST_RECEIVED_AT, System.currentTimeMillis())
                .putString(KEY_LAST_RECEIVED_SENDER, sender == null ? "" : sender)
                .apply();
    }

    public static boolean looksLikeDepositSms(String body) {
        String t = normalizeDigits(body == null ? "" : body).toLowerCase(Locale.US);
        boolean deposit = containsAny(t,
                "واریز", "واريز", "واریزی", "واريزی", "دریافت", "دريافت",
                "بستانکار", "بستانكار", "افزایش", "افزايش", "انتقال وجه کارتی",
                "انتقال وجه كارتی", "انتقال وجه کارت", "credited", "credit", "deposit", "received");
        boolean debit = containsAny(t,
                "برداشت", "خرید", "خريد", "کسر", "كسر", "پرداخت", "debit", "withdraw", "purchase");
        return deposit && !debit && extractAmountRial(body) > 0;
    }

    private static boolean containsAny(String text, String... needles) {
        for (String needle : needles) if (text.contains(needle.toLowerCase(Locale.US))) return true;
        return false;
    }

    public static String normalizeDigits(String value) {
        if (value == null) return "";
        char[] source = {'۰','۱','۲','۳','۴','۵','۶','۷','۸','۹','٠','١','٢','٣','٤','٥','٦','٧','٨','٩'};
        char[] target = {'0','1','2','3','4','5','6','7','8','9','0','1','2','3','4','5','6','7','8','9'};
        String out = value;
        for (int i = 0; i < source.length; i++) out = out.replace(source[i], target[i]);
        return out.replaceAll("[\u200C-\u200F\u202A-\u202E\u2066-\u2069]", " ")
                .replace('ي','ی').replace('ك','ک');
    }

    public static long extractAmountRial(String body) {
        String text = normalizeDigits(body == null ? "" : body);
        String[] parts = text.split("مانده|موجودی|balance|Balance|BAL|bal|available", 2);
        String head = parts.length > 0 ? parts[0] : text;

        long amount = firstLabelledAmount(head, "(ریال|ريال|rial|RIAL|irr|IRR)", 1L);
        if (amount > 0) return amount;
        amount = firstLabelledAmount(head, "(تومان|تومن|tmn|TMN|toman|TOMAN)", 10L);
        if (amount > 0) return amount;
        amount = firstAmountWithUnit(head, "(ریال|ريال|rial|RIAL|irr|IRR)", 1L);
        if (amount > 0) return amount;
        amount = firstAmountWithUnit(head, "(تومان|تومن|tmn|TMN|toman|TOMAN)", 10L);
        if (amount > 0) return amount;
        return firstAmountAfterDepositKeyword(head);
    }

    private static long firstAmountWithUnit(String text, String unitRegex, long multiplier) {
        Matcher matcher = Pattern.compile("([0-9][0-9,\\.\\s]{2,})\\s*" + unitRegex, Pattern.CASE_INSENSITIVE).matcher(text == null ? "" : text);
        while (matcher.find()) {
            long raw = cleanAmount(matcher.group(1));
            if (raw > 0 && raw * multiplier >= 10000) return raw * multiplier;
        }
        return 0L;
    }

    private static long firstLabelledAmount(String text, String unitRegex, long multiplier) {
        Matcher matcher = Pattern.compile("(?:مبلغ|amount|amt)\\s*[:：\\-ـ]?\\s*([0-9][0-9,\\.\\s]{2,})\\s*" + unitRegex, Pattern.CASE_INSENSITIVE).matcher(text == null ? "" : text);
        while (matcher.find()) {
            long raw = cleanAmount(matcher.group(1));
            if (raw > 0 && raw * multiplier >= 10000) return raw * multiplier;
        }
        return 0L;
    }

    private static long firstAmountAfterDepositKeyword(String text) {
        Matcher matcher = Pattern.compile("(?:واریز(?:ی)?|واريز(?:ی)?|دریافت|دريافت|بستانکار|بستانكار|افزایش|افزايش|credited|credit|deposit|received)\\s*[:：\\-ـ]?\\s*[+＋]?\\s*([0-9][0-9,\\.\\s]{2,})", Pattern.CASE_INSENSITIVE).matcher(text == null ? "" : text);
        while (matcher.find()) {
            long value = cleanAmount(matcher.group(1));
            if (value >= 10000) return value;
        }
        return 0L;
    }

    private static long cleanAmount(String value) {
        if (value == null) return 0L;
        String number = value.replaceAll("[^0-9]", "");
        if (number.length() == 0 || number.length() > 13) return 0L;
        try { return Long.parseLong(number); } catch (Exception ignored) { return 0L; }
    }

    public static void enqueueSms(Context context, String sender, String body, long receivedAtMillis, boolean recoveredByScan) {
        if (!looksLikeDepositSms(body)) return;
        Context app = context.getApplicationContext();
        synchronized (SmsUploader.class) {
            try {
                SharedPreferences prefs = app.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
                JSONArray current = new JSONArray(prefs.getString(KEY_QUEUE, "[]"));
                String hash = smsHash(sender, body, receivedAtMillis);
                for (int i = 0; i < current.length(); i++) {
                    JSONObject old = current.optJSONObject(i);
                    if (old != null && hash.equals(old.optString("hash"))) return;
                }
                JSONArray next = new JSONArray();
                int start = Math.max(0, current.length() - (MAX_QUEUE_SIZE - 1));
                for (int i = start; i < current.length(); i++) next.put(current.get(i));
                JSONObject item = new JSONObject();
                item.put("hash", hash);
                item.put("sender", sender == null ? "" : sender);
                item.put("body", body == null ? "" : body);
                item.put("received_at", receivedAtMillis > 0 ? receivedAtMillis : System.currentTimeMillis());
                item.put("manual_scan", recoveredByScan);
                item.put("attempts", 0);
                item.put("queued_at", System.currentTimeMillis());
                next.put(item);
                prefs.edit().putString(KEY_QUEUE, next.toString()).apply();
            } catch (Exception e) {
                saveError(app, "خطا در ذخیره صف: " + e.getMessage());
            }
        }
    }

    public static int queueCount(Context context) {
        try {
            return new JSONArray(context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString(KEY_QUEUE, "[]")).length();
        } catch (Exception ignored) { return 0; }
    }

    public static void scheduleImmediate(Context context) {
        try {
            Context app = context.getApplicationContext();
            JobScheduler scheduler = (JobScheduler) app.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            if (scheduler == null) return;
            JobInfo job = new JobInfo.Builder(JOB_IMMEDIATE, new ComponentName(app, SmsSyncJobService.class))
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                    .setMinimumLatency(250)
                    .setOverrideDeadline(3000)
                    .setBackoffCriteria(10000L, JobInfo.BACKOFF_POLICY_EXPONENTIAL)
                    .setPersisted(true)
                    .build();
            scheduler.schedule(job);
        } catch (Exception e) { saveError(context, "خطا در زمان‌بندی همگام‌سازی: " + e.getMessage()); }
    }

    public static void schedulePeriodicSafetyScan(Context context) {
        try {
            Context app = context.getApplicationContext();
            JobScheduler scheduler = (JobScheduler) app.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            if (scheduler == null) return;
            if (Build.VERSION.SDK_INT >= 24 && scheduler.getPendingJob(JOB_PERIODIC_SCAN) != null) return;
            JobInfo job = new JobInfo.Builder(JOB_PERIODIC_SCAN, new ComponentName(app, SmsSyncJobService.class))
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                    .setPeriodic(PERIODIC_INTERVAL_MS)
                    .setPersisted(true)
                    .build();
            scheduler.schedule(job);
        } catch (Exception e) { saveError(context, "خطا در فعال‌سازی بررسی دوره‌ای: " + e.getMessage()); }
    }

    public static boolean processQueueBlocking(Context context) {
        Context app = context.getApplicationContext();
        if (!isConfigured()) {
            saveError(app, "آدرس سرور یا توکن اپ پیامکی تنظیم نشده است.");
            return false;
        }
        boolean allSent = true;
        synchronized (SmsUploader.class) {
            try {
                SharedPreferences prefs = app.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
                JSONArray queue = new JSONArray(prefs.getString(KEY_QUEUE, "[]"));
                JSONArray keep = new JSONArray();
                for (int i = 0; i < queue.length(); i++) {
                    JSONObject item = queue.optJSONObject(i);
                    if (item == null) continue;
                    UploadResult result = uploadSmsBlocking(
                            item.optString("sender", ""),
                            item.optString("body", ""),
                            item.optLong("received_at", System.currentTimeMillis()),
                            item.optBoolean("manual_scan", false),
                            item.optString("hash", "")
                    );
                    if (result.accepted) {
                        prefs.edit()
                                .putLong(KEY_LAST_SUCCESS_AT, System.currentTimeMillis())
                                .putString(KEY_LAST_SERVER_STATUS, result.status)
                                .putString(KEY_LAST_ERROR, "")
                                .putLong(KEY_TOTAL_SENT, prefs.getLong(KEY_TOTAL_SENT, 0L) + 1L)
                                .apply();
                    } else {
                        item.put("attempts", item.optInt("attempts", 0) + 1);
                        item.put("last_error", result.message);
                        keep.put(item);
                        allSent = false;
                        saveError(app, result.message);
                    }
                }
                prefs.edit().putString(KEY_QUEUE, keep.toString()).commit();
            } catch (Exception e) {
                allSent = false;
                saveError(app, "خطا در پردازش صف: " + e.getMessage());
            }
        }
        if (!allSent) scheduleImmediate(app);
        return allSent;
    }

    private static UploadResult uploadSmsBlocking(String sender, String body, long receivedAtMillis, boolean manualScan, String knownHash) {
        HttpURLConnection connection = null;
        try {
            URL url = new URL(baseUrl() + "/api/sms_payment_ingest.php");
            JSONObject json = new JSONObject();
            json.put("sender", sender == null ? "" : sender);
            json.put("body", body == null ? "" : body);
            json.put("received_at", receivedAtMillis > 0 ? receivedAtMillis : System.currentTimeMillis());
            json.put("amount_rial", extractAmountRial(body));
            json.put("sms_hash", knownHash == null || knownHash.length() != 64 ? smsHash(sender, body, receivedAtMillis) : knownHash);
            json.put("manual_scan", manualScan);

            connection = openConnection(url);
            byte[] data = json.toString().getBytes("UTF-8");
            OutputStream out = connection.getOutputStream();
            out.write(data);
            out.flush();
            out.close();

            int code = connection.getResponseCode();
            String response = readResponse(connection, code);
            if (code >= 200 && code < 300) {
                JSONObject parsed = response.length() > 0 ? new JSONObject(response) : new JSONObject();
                boolean ok = parsed.optBoolean("ok", false);
                String status = parsed.optString("status", parsed.optString("message", "received"));
                if (ok) return new UploadResult(true, status, "");
                return new UploadResult(false, status, parsed.optString("error", "پاسخ نامعتبر سرور"));
            }
            return new UploadResult(false, "http_" + code, "خطای سرور HTTP " + code);
        } catch (Exception e) {
            return new UploadResult(false, "network_error", "عدم دسترسی به سرور: " + e.getMessage());
        } finally {
            if (connection != null) connection.disconnect();
        }
    }

    public static boolean pingServerBlocking(Context context) {
        HttpURLConnection connection = null;
        boolean ok = false;
        String message;
        try {
            URL url = new URL(baseUrl() + "/api/sms_payment_ingest.php");
            connection = openConnection(url);
            JSONObject json = new JSONObject();
            json.put("action", "ping");
            byte[] data = json.toString().getBytes("UTF-8");
            OutputStream out = connection.getOutputStream();
            out.write(data); out.flush(); out.close();
            int code = connection.getResponseCode();
            String response = readResponse(connection, code);
            JSONObject parsed = response.length() > 0 ? new JSONObject(response) : new JSONObject();
            ok = code >= 200 && code < 300 && parsed.optBoolean("ok", false);
            message = ok ? "اتصال امن با سرور برقرار است." : parsed.optString("error", "خطای HTTP " + code);
        } catch (Exception e) {
            message = "اتصال ناموفق: " + e.getMessage();
        } finally {
            if (connection != null) connection.disconnect();
        }
        SharedPreferences.Editor editor = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
                .putString(KEY_LAST_HEARTBEAT_MESSAGE, message);
        if (ok) editor.putLong(KEY_LAST_HEARTBEAT_AT, System.currentTimeMillis()).putString(KEY_LAST_ERROR, "");
        else editor.putString(KEY_LAST_ERROR, message);
        editor.apply();
        return ok;
    }

    public static void testServerAsync(Context context, TestCallback callback) {
        Context app = context.getApplicationContext();
        new Thread(() -> {
            boolean ok = pingServerBlocking(app);
            String message = app.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
                    .getString(KEY_LAST_HEARTBEAT_MESSAGE, ok ? "اتصال برقرار است." : "اتصال ناموفق است.");
            if (callback != null) callback.onDone(ok, message);
        }).start();
    }

    private static HttpURLConnection openConnection(URL url) throws Exception {
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setConnectTimeout(8000);
        connection.setReadTimeout(15000);
        connection.setDoOutput(true);
        connection.setUseCaches(false);
        connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
        connection.setRequestProperty("Accept", "application/json");
        connection.setRequestProperty("X-SMS-App-Token", BuildConfig.SMS_APP_TOKEN);
        connection.setRequestProperty("User-Agent", "GemAccSmsSync/" + BuildConfig.VERSION_NAME);
        return connection;
    }

    private static String readResponse(HttpURLConnection connection, int code) {
        StringBuilder result = new StringBuilder();
        try {
            InputStream stream = code >= 200 && code < 300 ? connection.getInputStream() : connection.getErrorStream();
            if (stream == null) return "";
            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
            String line;
            while ((line = reader.readLine()) != null) result.append(line);
            reader.close();
        } catch (Exception ignored) {}
        return result.toString();
    }

    public static void scanRecentSmsAsync(Context context, int hours, ScanCallback callback) {
        Context app = context.getApplicationContext();
        new Thread(() -> {
            int beforeScan = queueCount(app);
            int[] result = scanRecentSmsBlocking(app, hours);
            int beforeSend = queueCount(app);
            processQueueBlocking(app);
            int sent = Math.max(0, beforeSend - queueCount(app));
            int newlyQueued = Math.max(0, beforeSend - beforeScan);
            if (callback != null) callback.onDone(result[0], newlyQueued, sent);
        }).start();
    }

    public static int[] scanRecentSmsBlocking(Context context, int hours) {
        if (!hasReadSmsPermission(context)) return new int[]{0, 0};
        long since = System.currentTimeMillis() - Math.max(1, hours) * 3600000L;
        int scanned = 0;
        int before = queueCount(context);
        Cursor cursor = null;
        try {
            cursor = context.getContentResolver().query(
                    Uri.parse("content://sms/inbox"),
                    new String[]{"address", "body", "date"},
                    "date>=?",
                    new String[]{String.valueOf(since)},
                    "date DESC LIMIT 500"
            );
            if (cursor != null) {
                int senderIndex = cursor.getColumnIndex("address");
                int bodyIndex = cursor.getColumnIndex("body");
                int dateIndex = cursor.getColumnIndex("date");
                while (cursor.moveToNext()) {
                    scanned++;
                    String sender = senderIndex >= 0 ? cursor.getString(senderIndex) : "";
                    String body = bodyIndex >= 0 ? cursor.getString(bodyIndex) : "";
                    long date = dateIndex >= 0 ? cursor.getLong(dateIndex) : System.currentTimeMillis();
                    if (looksLikeDepositSms(body)) enqueueSms(context, sender, body, date, true);
                }
            }
            context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putLong(KEY_LAST_SCAN_AT, System.currentTimeMillis()).apply();
        } catch (Exception e) {
            saveError(context, "خطا در بررسی صندوق پیامک: " + e.getMessage());
        } finally {
            if (cursor != null) cursor.close();
        }
        return new int[]{scanned, Math.max(0, queueCount(context) - before)};
    }

    public static Status status(Context context) {
        SharedPreferences prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
        return new Status(
                queueCount(context),
                prefs.getLong(KEY_LAST_RECEIVED_AT, 0L),
                prefs.getString(KEY_LAST_RECEIVED_SENDER, ""),
                prefs.getLong(KEY_LAST_SUCCESS_AT, 0L),
                prefs.getLong(KEY_LAST_SCAN_AT, 0L),
                prefs.getString(KEY_LAST_SERVER_STATUS, ""),
                prefs.getString(KEY_LAST_ERROR, ""),
                prefs.getLong(KEY_TOTAL_SENT, 0L),
                prefs.getLong(KEY_LAST_HEARTBEAT_AT, 0L),
                prefs.getString(KEY_LAST_HEARTBEAT_MESSAGE, "")
        );
    }

    private static void saveError(Context context, String error) {
        context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putString(KEY_LAST_ERROR, error == null ? "" : error).apply();
    }

    private static String smsHash(String sender, String body, long receivedAtMillis) throws Exception {
        return sha256((sender == null ? "" : sender) + "|" + receivedAtMillis + "|" + normalizeDigits(body == null ? "" : body));
    }

    private static String sha256(String value) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] bytes = digest.digest(value.getBytes("UTF-8"));
        StringBuilder out = new StringBuilder();
        for (byte b : bytes) out.append(String.format(Locale.US, "%02x", b));
        return out.toString();
    }

    private static final class UploadResult {
        final boolean accepted;
        final String status;
        final String message;
        UploadResult(boolean accepted, String status, String message) {
            this.accepted = accepted; this.status = status == null ? "" : status; this.message = message == null ? "" : message;
        }
    }

    public static final class Status {
        public final int queueCount;
        public final long lastReceivedAt;
        public final String lastSender;
        public final long lastSuccessAt;
        public final long lastScanAt;
        public final String lastServerStatus;
        public final String lastError;
        public final long totalSent;
        public final long lastHeartbeatAt;
        public final String lastHeartbeatMessage;

        Status(int queueCount, long lastReceivedAt, String lastSender, long lastSuccessAt, long lastScanAt,
               String lastServerStatus, String lastError, long totalSent, long lastHeartbeatAt, String lastHeartbeatMessage) {
            this.queueCount = queueCount;
            this.lastReceivedAt = lastReceivedAt;
            this.lastSender = lastSender;
            this.lastSuccessAt = lastSuccessAt;
            this.lastScanAt = lastScanAt;
            this.lastServerStatus = lastServerStatus;
            this.lastError = lastError;
            this.totalSent = totalSent;
            this.lastHeartbeatAt = lastHeartbeatAt;
            this.lastHeartbeatMessage = lastHeartbeatMessage;
        }
    }
}
