package com.miraclegarden.smsmessage.Activity;

import android.app.DatePickerDialog;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;

import com.miraclegarden.library.app.MiracleGardenActivity;
import com.miraclegarden.smsmessage.R;
import com.miraclegarden.smsmessage.comm.CommonAdapter;
import com.miraclegarden.smsmessage.comm.ViewHolder;
import com.miraclegarden.smsmessage.databinding.ActivityMmpClaimBinding;
import com.miraclegarden.smsmessage.mmp.MmpHookStatus;
import com.miraclegarden.smsmessage.mmp.MmpLocalSettings;
import com.miraclegarden.smsmessage.mmp.MmpModuleInfo;
import com.miraclegarden.smsmessage.mmp.MmpPacket;
import com.miraclegarden.smsmessage.mmp.MmpPacketStore;
import com.miraclegarden.smsmessage.mmp.MmpSettingsClient;
import com.miraclegarden.smsmessage.mmp.MmpSyncClient;
import com.miraclegarden.smsmessage.mmp.TngProcessHelper;

import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

/**
 * 手机端红包领取台（独立于通用消息/情况监听）。
 */
public class MmpClaimActivity extends MiracleGardenActivity<ActivityMmpClaimBinding>
        implements MmpPacketStore.Listener {

    private final List<MmpPacket> allPackets = new ArrayList<>();
    private final List<MmpPacket> packets = new ArrayList<>();
    private CommonAdapter<MmpPacket> adapter;
    private final Handler handler = new Handler(Looper.getMainLooper());
    private final Runnable refreshRunnable = this::reload;
    /** 0全部 1领取中 2已领完 */
    private int statusFilter = 0;
    /** 0全部时间 1近1天 2近3天 3近7天 4自定义 */
    private int timeMode = 0;
    private long customFromMs;
    private long customToMs;
    private String keyword = "";
    private boolean settingsOpen;
    private boolean autoWatchWhileOpen = true;
    private boolean autoLaunchTngIfKilled = true;
    private long lastTngLaunchAt;
    private final Runnable watchRunnable = this::watchTngOnce;
    /** null=检测中 true=通 false=不通 */
    private Boolean debugServerOk;
    private long lastDebugPingAt;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        JSONObject local = MmpLocalSettings.load(this);
        autoWatchWhileOpen = local.optBoolean("autoWatchWhileOpen", true);
        autoLaunchTngIfKilled = local.optBoolean("autoLaunchTngIfKilled", true);
        initView();
        reload();
        loadSettingsQuiet();
    }

    @Override
    protected void onResume() {
        super.onResume();
        MmpPacketStore.addListener(this);
        reload();
        MmpSyncClient.syncIfStale(this, 5000L);
        handler.removeCallbacks(refreshRunnable);
        handler.postDelayed(refreshRunnable, 2000);
        scheduleWatch();
        updateConnStatus();
        pingDebugServer();
    }

    @Override
    protected void onPause() {
        super.onPause();
        MmpPacketStore.removeListener(this);
        handler.removeCallbacks(refreshRunnable);
        handler.removeCallbacks(watchRunnable);
    }

    @Override
    public void onMmpPacketsChanged() {
        runOnUiThread(() -> {
            reload();
            updateConnStatus();
        });
    }

    private void initView() {
        binding.backIv.setOnClickListener(v -> finish());
        binding.btnClear.setOnClickListener(v -> confirmClear());
        binding.btnHelp.setOnClickListener(v ->
                startActivity(new android.content.Intent(this, MmpHelpActivity.class)));
        binding.btnSettings.setOnClickListener(v -> toggleSettings());
        binding.btnCloseSettings.setOnClickListener(v -> {
            settingsOpen = false;
            binding.settingsPanel.setVisibility(View.GONE);
        });
        binding.btnSync.setOnClickListener(v -> syncNow());
        binding.btnRefresh.setOnClickListener(v -> manualRefresh());

        binding.chipAll.setOnClickListener(v -> setStatusFilter(0));
        binding.chipOpen.setOnClickListener(v -> setStatusFilter(1));
        binding.chipDone.setOnClickListener(v -> setStatusFilter(2));
        binding.chipTimeAll.setOnClickListener(v -> setTimeMode(0));
        binding.chipTime1.setOnClickListener(v -> setTimeMode(1));
        binding.chipTime3.setOnClickListener(v -> setTimeMode(2));
        binding.chipTime7.setOnClickListener(v -> setTimeMode(3));
        binding.chipTimeCustom.setOnClickListener(v -> setTimeMode(4));
        binding.btnDateFrom.setOnClickListener(v -> pickDate(true));
        binding.btnDateTo.setOnClickListener(v -> pickDate(false));

        binding.etFilter.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                keyword = s != null ? s.toString() : "";
                applyFilter();
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        binding.btnPresetFast.setOnClickListener(v -> applyPreset("fast"));
        binding.btnPresetNormal.setOnClickListener(v -> applyPreset("normal"));
        binding.btnPresetSlow.setOnClickListener(v -> applyPreset("slow"));
        binding.btnSaveSettings.setOnClickListener(v -> saveSettings());

        binding.recyclerview.setLayoutManager(new LinearLayoutManager(this));
        adapter = new CommonAdapter<MmpPacket>(this, R.layout.item_mmp_packet, packets) {
            @Override
            public void convert(ViewHolder holder, MmpPacket packet, int index) {
                holder.setText(R.id.tv_title, packet.displayTitle());
                holder.setText(R.id.tv_sum, formatMoney(packet.sumClaimed > 0
                        ? packet.sumClaimed : parseDouble(packet.total)));

                TextView idView = holder.getView(R.id.tv_id);
                if (!TextUtils.isEmpty(packet.packetId)) {
                    idView.setVisibility(View.VISIBLE);
                    idView.setText("ID " + packet.packetId);
                } else {
                    idView.setVisibility(View.GONE);
                    idView.setText("");
                }

                android.widget.TextView status = holder.getView(R.id.tv_status);
                status.setText(packet.statusLabel());
                if (packet.isFullyClaimed()) {
                    status.setTextColor(Color.parseColor("#047857"));
                    status.setBackgroundColor(Color.parseColor("#ECFDF5"));
                } else if (packet.isExpired()) {
                    status.setTextColor(Color.parseColor("#6B7280"));
                    status.setBackgroundColor(Color.parseColor("#F3F4F6"));
                } else {
                    status.setTextColor(Color.parseColor("#B45309"));
                    status.setBackgroundColor(Color.parseColor("#FFFBEB"));
                }

                String meta;
                if (packet.effectiveTotalCount() > 0) {
                    meta = packet.effectiveClaimedCount() + "/" + packet.effectiveTotalCount() + " 人";
                } else {
                    meta = packet.claimantCount + " 人";
                }
                if (packet.issuedAt != null && !packet.issuedAt.isEmpty()) {
                    meta += " · 发放 " + packet.issuedAt;
                }
                if (packet.expiresAt != null && !packet.expiresAt.isEmpty()) {
                    meta += " · 到期 " + packet.expiresAt;
                }
                holder.setText(R.id.tv_meta, meta);

                String hi = packet.finished ? "最佳" : "最高";
                String lo = packet.finished ? "最差" : "最低";
                com.miraclegarden.smsmessage.mmp.MmpClaim best = packet.bestClaim();
                com.miraclegarden.smsmessage.mmp.MmpClaim worst = packet.worstClaim();
                holder.setText(R.id.tv_best, formatHiLoLine(hi, best));
                if (worst != null && packet.claimantCount > 1) {
                    holder.setText(R.id.tv_worst, formatHiLoLine(lo, worst));
                } else {
                    holder.setText(R.id.tv_worst, lo + " -");
                }
                holder.itemView.setOnClickListener(v -> openDetail(packet.packetId));
            }
        };
        binding.recyclerview.setAdapter(adapter);
        updateChipUi();
        updateTimeChipUi();
    }

    private void reload() {
        allPackets.clear();
        allPackets.addAll(MmpPacketStore.getPackets(this));
        applyFilter();
        updateConnStatus();
        handler.removeCallbacks(refreshRunnable);
        handler.postDelayed(refreshRunnable, 2000);
    }

    private void applyFilter() {
        packets.clear();
        for (MmpPacket p : allPackets) {
            if (p.matchesFilter(keyword, statusFilter, timeMode, customFromMs, customToMs)) {
                packets.add(p);
            }
        }
        adapter.notifyDataSetChanged();
        binding.tvCount.setText(packets.size() + "/" + allPackets.size() + " 个");
        binding.emptyLy.setVisibility(packets.isEmpty() ? View.VISIBLE : View.GONE);
    }

    private void setStatusFilter(int filter) {
        statusFilter = filter;
        updateChipUi();
        applyFilter();
    }

    private void setTimeMode(int mode) {
        timeMode = mode;
        binding.customRangeLy.setVisibility(mode == 4 ? View.VISIBLE : View.GONE);
        updateTimeChipUi();
        applyFilter();
    }

    private void pickDate(boolean from) {
        Calendar cal = Calendar.getInstance();
        long base = from ? customFromMs : customToMs;
        if (base > 0) {
            cal.setTimeInMillis(base);
        }
        new DatePickerDialog(this, (view, y, m, d) -> {
            Calendar c = Calendar.getInstance();
            if (from) {
                c.set(y, m, d, 0, 0, 0);
                c.set(Calendar.MILLISECOND, 0);
                customFromMs = c.getTimeInMillis();
                binding.btnDateFrom.setText(String.format(Locale.US, "%04d-%02d-%02d", y, m + 1, d));
            } else {
                c.set(y, m, d, 23, 59, 59);
                c.set(Calendar.MILLISECOND, 999);
                customToMs = c.getTimeInMillis();
                binding.btnDateTo.setText(String.format(Locale.US, "%04d-%02d-%02d", y, m + 1, d));
            }
            if (timeMode != 4) {
                setTimeMode(4);
            } else {
                applyFilter();
            }
        }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show();
    }

    private void updateChipUi() {
        styleChip(binding.chipAll, statusFilter == 0, true);
        styleChip(binding.chipOpen, statusFilter == 1, true);
        styleChip(binding.chipDone, statusFilter == 2, true);
    }

    private void updateTimeChipUi() {
        styleChip(binding.chipTimeAll, timeMode == 0, false);
        styleChip(binding.chipTime1, timeMode == 1, false);
        styleChip(binding.chipTime3, timeMode == 2, false);
        styleChip(binding.chipTime7, timeMode == 3, false);
        styleChip(binding.chipTimeCustom, timeMode == 4, false);
    }

    private static void styleChip(View chip, boolean active, boolean brand) {
        if (!(chip instanceof android.widget.TextView)) {
            return;
        }
        android.widget.TextView tv = (android.widget.TextView) chip;
        if (active) {
            tv.setBackgroundColor(Color.parseColor(brand ? "#C45C26" : "#1C1410"));
            tv.setTextColor(Color.WHITE);
        } else {
            tv.setBackgroundColor(Color.parseColor("#EEEEEE"));
            tv.setTextColor(Color.parseColor("#333333"));
        }
    }

    private void syncNow() {
        Toast.makeText(this, "正在同步到电脑…", Toast.LENGTH_SHORT).show();
        MmpSyncClient.syncToPc(this, (ok, message) -> runOnUiThread(() ->
                Toast.makeText(this, message != null ? message : (ok ? "同步成功" : "同步失败"),
                        Toast.LENGTH_SHORT).show()));
    }

    private void manualRefresh() {
        reload();
        MmpSyncClient.syncIfStale(this, 0L);
        boolean alive = TngProcessHelper.isRunning(this);
        String tip = "已刷新，当前 " + packets.size() + "/" + allPackets.size() + " 个";
        if (!alive) {
            tip += "；TNG 未运行";
            if (autoLaunchTngIfKilled) {
                if (TngProcessHelper.launch(this)) {
                    lastTngLaunchAt = System.currentTimeMillis();
                    tip += "，已尝试打开";
                } else {
                    tip += "，打开失败";
                }
            }
        } else {
            tip += "；TNG 在跑";
        }
        Toast.makeText(this, tip, Toast.LENGTH_SHORT).show();
        updateConnStatus();
        pingDebugServer();
        scheduleWatch();
    }

    private void scheduleWatch() {
        handler.removeCallbacks(watchRunnable);
        handler.postDelayed(watchRunnable, 3_000L);
    }

    private void watchTngOnce() {
        if (isFinishing()) {
            return;
        }
        boolean alive = TngProcessHelper.isRunning(this);
        if (autoWatchWhileOpen && !alive && autoLaunchTngIfKilled) {
            long now = System.currentTimeMillis();
            if (now - lastTngLaunchAt > 60_000L) {
                if (TngProcessHelper.launch(this)) {
                    lastTngLaunchAt = now;
                    Toast.makeText(this, "检测到 TNG 已退出，正在重新打开…", Toast.LENGTH_SHORT).show();
                }
            }
        }
        updateConnStatus();
        if (System.currentTimeMillis() - lastDebugPingAt > 20_000L) {
            pingDebugServer();
        }
        handler.removeCallbacks(watchRunnable);
        handler.postDelayed(watchRunnable, 12_000L);
    }

    private void pingDebugServer() {
        lastDebugPingAt = System.currentTimeMillis();
        MmpSettingsClient.pingHealth(ok -> runOnUiThread(() -> {
            debugServerOk = ok;
            updateConnStatus();
        }));
    }

    private void updateConnStatus() {
        if (binding.tvConnStatus == null) {
            return;
        }
        boolean tngAlive = TngProcessHelper.isRunning(this);
        long lastIngest = MmpPacketStore.lastIngestAt(this);
        long ageMs = lastIngest > 0 ? System.currentTimeMillis() - lastIngest : -1L;
        long hookAge = MmpHookStatus.lastAliveAt(this) > 0
                ? System.currentTimeMillis() - MmpHookStatus.lastAliveAt(this) : -1L;
        // 有近期心跳 = Hook 活着（定时拉取会报活）；久无入库不等于故障
        boolean hookAlive = hookAge >= 0 && hookAge <= 3 * 60_000L;
        boolean modLatest = MmpHookStatus.liveVersionCode(this) >= 0
                && MmpHookStatus.isLiveLatest(this);
        boolean modKnownOld = hookAlive
                && MmpHookStatus.liveVersionCode(this) > 0
                && MmpHookStatus.liveVersionCode(this) < MmpModuleInfo.EXPECTED_VERSION_CODE;

        String tngPart = tngAlive ? "TNG：运行中" : "TNG：未运行";
        String hookPart;
        if (!tngAlive) {
            hookPart = "Hook：未连接（TNG 未开）";
        } else if (hookAlive && ageMs >= 0 && ageMs <= 120_000L) {
            hookPart = "Hook：正常（" + formatAge(ageMs) + "前有数据）";
        } else if (hookAlive) {
            if (ageMs >= 0) {
                hookPart = "Hook：正常（定时拉取中，上次数据 " + formatAge(ageMs) + "前）";
            } else {
                hookPart = "Hook：正常（定时拉取中，尚无入库）";
            }
        } else if (ageMs >= 0 && ageMs <= 10 * 60_000L) {
            hookPart = "Hook：待确认（" + formatAge(ageMs) + "前有数据，心跳偏旧）";
        } else if (ageMs < 0) {
            hookPart = "Hook：待确认（无心跳，尚无入库）";
        } else {
            hookPart = "Hook：异常（无心跳，" + formatAge(ageMs) + "前曾有数据）";
        }

        String watchPart = autoWatchWhileOpen
                ? (autoLaunchTngIfKilled ? "检测：开（杀掉会拉起）" : "检测：开（不自动拉起）")
                : "检测：关";

        String debugPart;
        if (debugServerOk == null) {
            debugPart = "调试台：检测中（可选）";
        } else if (debugServerOk) {
            debugPart = "调试台：已连接（可选）";
        } else {
            debugPart = "调试台：未连（可选，不影响领取）";
        }

        String text = tngPart + "  ·  " + hookPart + "\n"
                + watchPart + "  ·  " + debugPart + "\n"
                + MmpHookStatus.describe(this);
        binding.tvConnStatus.setText(text);

        // 颜色只看 TNG / Hook 心跳 / 模块版本；调试台可有可无，绝不因此爆红
        boolean okAll = tngAlive && hookAlive && modLatest;
        boolean bad = !tngAlive || !hookAlive || modKnownOld;
        if (okAll) {
            binding.tvConnStatus.setBackgroundColor(Color.parseColor("#064E3B"));
            binding.tvConnStatus.setTextColor(Color.parseColor("#D1FAE5"));
        } else if (bad) {
            binding.tvConnStatus.setBackgroundColor(Color.parseColor("#7F1D1D"));
            binding.tvConnStatus.setTextColor(Color.parseColor("#FEE2E2"));
        } else {
            binding.tvConnStatus.setBackgroundColor(Color.parseColor("#78350F"));
            binding.tvConnStatus.setTextColor(Color.parseColor("#FEF3C7"));
        }
    }

    private static String formatAge(long ageMs) {
        if (ageMs < 60_000L) {
            return Math.max(1, ageMs / 1000) + "秒";
        }
        if (ageMs < 3600_000L) {
            return (ageMs / 60_000L) + "分钟";
        }
        return (ageMs / 3600_000L) + "小时";
    }

    private void toggleSettings() {
        settingsOpen = !settingsOpen;
        binding.settingsPanel.setVisibility(settingsOpen ? View.VISIBLE : View.GONE);
        if (settingsOpen) {
            loadSettingsQuiet();
        }
    }

    private void loadSettingsQuiet() {
        MmpSettingsClient.load(this, (json, error) -> {
            if (json != null) {
                fillSettingsForm(json);
                if (error != null && !error.isEmpty()) {
                    binding.tvSettingsStatus.setText(error);
                } else {
                    binding.tvSettingsStatus.setText("设置已加载（本地优先，可离线保存）");
                }
            } else {
                fillSettingsForm(MmpLocalSettings.load(this));
                binding.tvSettingsStatus.setText(error != null ? error : "已用本地默认设置");
            }
        });
    }

    private void fillSettingsForm(JSONObject json) {
        binding.cfgHistory.setText(String.valueOf(json.optInt("historyCooldownSec", 8)));
        binding.cfgDetail.setText(String.valueOf(json.optInt("detailCooldownSec", 8)));
        binding.cfgDedup.setText(String.valueOf(json.optInt("dedupSec", 3)));
        binding.cfgGap.setText(String.valueOf(json.optInt("detailGapMs", 80)));
        binding.cfgOpenHistory.setChecked(json.optBoolean("openHistoryIfNoTemplate", true));
        binding.cfgBounceHistory.setChecked(json.optBoolean("autoBounceHistoryOnDisconnect", true));
        binding.cfgAutoWatch.setChecked(json.optBoolean("autoWatchWhileOpen", true));
        binding.cfgAutoLaunch.setChecked(json.optBoolean("autoLaunchTngIfKilled", true));
        autoWatchWhileOpen = binding.cfgAutoWatch.isChecked();
        autoLaunchTngIfKilled = binding.cfgAutoLaunch.isChecked();
    }

    private void applyPreset(String name) {
        try {
            JSONObject o = MmpLocalSettings.defaults();
            if ("fast".equals(name)) {
                o.put("historyCooldownSec", 5);
                o.put("detailCooldownSec", 5);
                o.put("dedupSec", 2);
                o.put("detailGapMs", 50);
                o.put("pagePollMs", 800);
            } else if ("slow".equals(name)) {
                o.put("historyCooldownSec", 30);
                o.put("detailCooldownSec", 45);
                o.put("dedupSec", 15);
                o.put("detailGapMs", 200);
                o.put("pagePollMs", 3000);
            }
            o.put("openHistoryIfNoTemplate", true);
            o.put("autoBounceHistoryOnDisconnect", true);
            o.put("autoWatchWhileOpen", true);
            o.put("autoLaunchTngIfKilled", true);
            fillSettingsForm(o);
            binding.tvSettingsStatus.setText("已套用预设，点保存生效");
        } catch (Exception e) {
            binding.tvSettingsStatus.setText("预设失败");
        }
    }

    private void saveSettings() {
        try {
            JSONObject body = new JSONObject();
            body.put("historyCooldownSec", parseIntSafe(binding.cfgHistory.getText().toString(), 8));
            body.put("detailCooldownSec", parseIntSafe(binding.cfgDetail.getText().toString(), 8));
            body.put("dedupSec", parseIntSafe(binding.cfgDedup.getText().toString(), 3));
            body.put("detailGapMs", parseIntSafe(binding.cfgGap.getText().toString(), 80));
            body.put("pagePollMs", 1000);
            body.put("openHistoryIfNoTemplate", binding.cfgOpenHistory.isChecked());
            body.put("autoBounceHistoryOnDisconnect", binding.cfgBounceHistory.isChecked());
            body.put("autoWatchWhileOpen", binding.cfgAutoWatch.isChecked());
            body.put("autoLaunchTngIfKilled", binding.cfgAutoLaunch.isChecked());
            autoWatchWhileOpen = binding.cfgAutoWatch.isChecked();
            autoLaunchTngIfKilled = binding.cfgAutoLaunch.isChecked();
            binding.tvSettingsStatus.setText("保存中…");
            MmpSettingsClient.save(this, body, (json, error) -> {
                if (json != null) {
                    fillSettingsForm(json);
                    String msg = error != null ? error : "已保存；Hook 参数约 10 秒内生效（需调试台）";
                    binding.tvSettingsStatus.setText(msg);
                    Toast.makeText(this, "设置已保存", Toast.LENGTH_SHORT).show();
                    scheduleWatch();
                } else {
                    binding.tvSettingsStatus.setText("保存失败：" + error);
                    Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show();
                }
            });
        } catch (Exception e) {
            binding.tvSettingsStatus.setText("保存异常");
        }
    }

    private void openDetail(String packetId) {
        Intent intent = new Intent(this, MmpDetailActivity.class);
        intent.putExtra(MmpDetailActivity.EXTRA_PACKET_ID, packetId);
        startActivity(intent);
    }

    private static String formatHiLoLine(String prefix,
                                         com.miraclegarden.smsmessage.mmp.MmpClaim claim) {
        if (claim == null) {
            return prefix + " -";
        }
        String nick = claim.nickname != null ? claim.nickname : "?";
        String amt = claim.amountText != null ? claim.amountText : formatMoney(claim.amount);
        if (!TextUtils.isEmpty(claim.userId)) {
            return prefix + " " + nick + " " + amt + "\nID " + claim.userId;
        }
        return prefix + " " + nick + " " + amt;
    }

    private void confirmClear() {
        new AlertDialog.Builder(this)
                .setTitle("清空红包台")
                .setMessage("仅清空本机红包领取记录，不影响通用消息台。")
                .setPositiveButton("清空", (d, w) -> {
                    MmpPacketStore.clear(this);
                    Toast.makeText(this, "已清空", Toast.LENGTH_SHORT).show();
                    reload();
                })
                .setNegativeButton("取消", null)
                .show();
    }

    private static String formatMoney(double v) {
        return String.format(Locale.US, "%.2f", v);
    }

    private static double parseDouble(String s) {
        try {
            return s == null || s.isEmpty() ? 0 : Double.parseDouble(s);
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private static int parseIntSafe(String s, int def) {
        try {
            return Integer.parseInt(s.trim());
        } catch (Exception e) {
            return def;
        }
    }
}
