# -*- coding: utf-8 -*-
"""
pytcper TCP调试助手 - TCP 客户端标签页
======================================
连接远程服务器并收发指令,支持自动重连、连接代际令牌与发送超时。
"""

import socket
import threading
import tkinter as tk
from tkinter import messagebox, ttk

from base_tab import BaseTab
from tcp_utils import SOCK_TIMEOUT, close_socket


class ClientTab(BaseTab):
    """TCP 客户端:连接远程服务器并收发指令,支持自动重连。"""

    def __init__(self, master):
        self._user_disconnect = True    # 是否为用户主动断开(主动断开时不触发自动重连)
        self._reconnect_after = None    # 待执行的自动重连 after 句柄
        self._conn_epoch = 0            # 连接代际令牌:每次发起/断开连接递增,作废旧线程的过期消息
        self._connecting = False        # 是否正在连接(防止重复点击启动多个连接线程)
        super().__init__(master, build_top=self._build_top)

    def _build_top(self):
        top = ttk.Frame(self)
        top.pack(fill="x", padx=8, pady=(8, 2))
        ttk.Label(top, text="服务器地址:").pack(side="left")
        self.var_host = tk.StringVar(value="127.0.0.1")
        ttk.Entry(top, textvariable=self.var_host, width=16).pack(side="left", padx=(2, 8))
        ttk.Label(top, text="端口:").pack(side="left")
        self.var_port = tk.StringVar(value="8080")
        ttk.Entry(top, textvariable=self.var_port, width=8).pack(side="left", padx=(2, 8))
        self.conn_btn = ttk.Button(top, text="连接", command=self.toggle_connect)
        self.conn_btn.pack(side="left", padx=(4, 8))
        self.conn_state = tk.StringVar(value="未连接")
        ttk.Label(top, textvariable=self.conn_state).pack(side="left")

        row2 = ttk.Frame(self)
        row2.pack(fill="x", padx=8, pady=(0, 2))
        self.var_autoreconnect = tk.BooleanVar(value=False)
        ttk.Checkbutton(row2, text="自动重连", variable=self.var_autoreconnect).pack(side="left")
        ttk.Label(row2, text="间隔(秒):").pack(side="left", padx=(8, 0))
        self.var_reconnect_interval = tk.StringVar(value="3")
        ttk.Entry(row2, textvariable=self.var_reconnect_interval, width=6).pack(side="left", padx=(2, 0))

    # ---------- 连接 / 断开 ----------
    def toggle_connect(self):
        if self.sock is not None:
            self._disconnect()
            return
        if self._connecting:            # 已有连接线程在途,忽略重复点击
            return
        if self._reconnect_after is not None:   # 重连等待中:点击即取消
            self._cancel_reconnect()
            self.conn_btn.configure(text="连接")
            self.conn_state.set("未连接")
            self.sys_log("已取消自动重连")
            return
        host = self.var_host.get().strip()
        try:
            port = int(self.var_port.get().strip())
        except ValueError:
            messagebox.showerror("错误", "端口必须是数字")
            return
        if not host:
            messagebox.showerror("错误", "请输入服务器地址")
            return
        self._user_disconnect = False
        self.conn_state.set("正在连接 %s:%d ..." % (host, port))
        self._start_connect(host, port)

    def _start_connect(self, host, port):
        """发起一次新连接:递增代际令牌并启动后台连接线程。"""
        self._conn_epoch += 1
        epoch = self._conn_epoch
        self._connecting = True
        threading.Thread(target=self._connect_worker,
                         args=(host, port, epoch), daemon=True).start()

    def _connect_worker(self, host, port, epoch):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)
        try:
            sock.connect((host, port))
        except OSError as exc:
            sock.close()
            self.log_msg("sys", "连接 %s:%d 失败: %s" % (host, port, exc))
            self.msg_queue.put(("connect_failed", epoch))
            return
        if self.closed:
            sock.close()
            return
        sock.settimeout(SOCK_TIMEOUT)
        self.sock = sock
        self.msg_queue.put(("connect_ok", (epoch, "%s:%d" % (host, port))))
        self.log_msg("sys", "已连接到 %s:%d" % (host, port))
        threading.Thread(target=self._recv_loop,
                         args=(sock, "%s:%d" % (host, port), epoch), daemon=True).start()

    def _recv_loop(self, sock, addr, epoch):
        dec_state = None
        while self.sock is sock:
            try:
                data = sock.recv(4096)
            except socket.timeout:
                continue
            except OSError:
                break
            if not data:
                break
            text, dec_state = self.decode_stream(dec_state, data)
            self.bump_stats("rx", data)
            self.log_msg("rx", text, addr)
        if self.sock is sock:
            self.sock = None
            self.msg_queue.put(("conn_lost", epoch))
            self.log_msg("sys", "连接已断开")
        try:
            sock.close()
        except OSError:
            pass

    def _disconnect(self):
        self._user_disconnect = True
        self._conn_epoch += 1           # 作废在途连接线程的过期消息
        self._connecting = False
        self._cancel_reconnect()
        sock, self.sock = self.sock, None
        close_socket(sock)
        self.conn_btn.configure(text="连接")
        self.conn_state.set("未连接")
        self.sys_log("已断开连接")

    # ---------- 自动重连 ----------
    def _schedule_reconnect(self):
        if self.sock is not None or self.closed:
            return
        if not self.var_autoreconnect.get():
            return
        try:
            delay = max(1.0, float(self.var_reconnect_interval.get()))
        except ValueError:
            delay = 3.0
        self.conn_btn.configure(text="取消重连")
        self.conn_state.set("未连接(%.0f 秒后自动重连)" % delay)
        self._reconnect_after = self.after(int(delay * 1000), self._reconnect_tick)

    def _reconnect_tick(self):
        self._reconnect_after = None
        if self.closed or self.sock is not None or not self.var_autoreconnect.get():
            return
        host = self.var_host.get().strip()
        try:
            port = int(self.var_port.get().strip())
        except ValueError:
            self.conn_btn.configure(text="连接")
            self.conn_state.set("未连接")
            self.sys_log("自动重连已停止(端口无效)")
            return
        self._user_disconnect = False
        self.conn_state.set("正在重连 %s:%d ..." % (host, port))
        self._start_connect(host, port)

    def _cancel_reconnect(self):
        if self._reconnect_after is not None:
            try:
                self.after_cancel(self._reconnect_after)
            except Exception:
                pass
            self._reconnect_after = None

    # ---------- 发送 ----------
    def do_send(self, silent=False):
        prepared = self.prepare_send(silent)
        if prepared is None:
            return False
        raw, data = prepared
        if self.sock is None:
            if not silent:
                messagebox.showwarning("提示", "尚未连接到服务器")
            return False
        if not silent:
            self.record_history(raw)
        exc = self.sendall_safe(self.sock, data)
        if exc is not None:
            self.sys_log("发送失败: %s" % exc)
            return False
        self.log_msg("tx", self.tx_text(data), "本机→服务器")
        self.bump_stats("tx", data)
        return True

    def _send_raw(self, data, desc):
        if self.sock is None:
            messagebox.showwarning("提示", "尚未连接到服务器")
            return False
        exc = self.sendall_safe(self.sock, data)
        if exc is not None:
            self.sys_log("发送 %s 失败: %s" % (desc, exc))
            return False
        self.log_msg("tx", self.tx_text(data), desc)
        self.bump_stats("tx", data)
        return True

    # ---------- 消息处理 ----------
    def handle_msg(self, kind, payload):
        if kind == "connect_ok":
            epoch, addr = payload
            if epoch != self._conn_epoch:
                return  # 过期的连接成功消息(已被更新的一次连接取代)
            self._connecting = False
            self._cancel_reconnect()
            self.conn_btn.configure(text="断开")
            self.conn_state.set("已连接 %s" % addr)
        elif kind == "conn_lost":
            epoch = payload
            if epoch != self._conn_epoch:
                return  # 过期的断线消息(旧连接已被替换)
            self._connecting = False
            self.conn_btn.configure(text="连接")
            self.conn_state.set("未连接")
            if self.var_autoreconnect.get() and not self._user_disconnect:
                self._schedule_reconnect()
        elif kind == "connect_failed":
            epoch = payload
            if epoch != self._conn_epoch:
                return  # 过期的连接失败消息(不影响当前已成功的连接)
            self._connecting = False
            self.conn_btn.configure(text="连接")
            self.conn_state.set("未连接")
            if self.var_autoreconnect.get() and not self._user_disconnect:
                self._schedule_reconnect()

    def close_conns(self):
        self._conn_epoch += 1           # 作废在途连接线程的过期消息
        self._connecting = False
        self._cancel_reconnect()
        super().close_conns()