package com.ttstd.signaling.controller;

import com.ttstd.signaling.config.BearerAuthFilter;
import com.ttstd.signaling.manager.SessionManager;
import com.ttstd.signaling.model.AccountStatus;
import com.ttstd.signaling.model.AuthPrincipal;
import com.ttstd.signaling.model.DeviceAccount;
import com.ttstd.signaling.model.DeviceBinding;
import com.ttstd.signaling.model.PrincipalType;
import com.ttstd.signaling.security.AuthException;
import com.ttstd.signaling.security.JwtService;
import com.ttstd.signaling.security.SecurityProperties;
import com.ttstd.signaling.service.AbuseReportService;
import com.ttstd.signaling.service.BindingService;
import com.ttstd.signaling.service.DeviceIdentityService;
import com.ttstd.signaling.service.PairingService;
import com.ttstd.signaling.service.TurnCredentialService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 客户端自助接口：供 WebRTCController / WebRTCControlled / Web / Flutter / iOS 五端对接所用。
 *
 * <p>所有接口均需在请求中携带有效令牌（{@code Authorization: Bearer <token>}），
 * 由 {@link BearerAuthFilter} 统一解析并写入主体信息；令牌缺失或非法将返回 401。
 *
 * <p>包含：
 * <ul>
 *   <li>{@code /verify}        —— 令牌校验（用户/设备令牌通用），返回有效性、过期时间与剩余秒数；
 *   <li>{@code /device/me}     —— 设备自助信息，被控端查看自身状态与在线情况；
 *   <li>{@code /account/me}    —— 账号自助信息，主控端查看自身状态与双因子开关；
 *   <li>{@code /ws-info}       —— 握手指引，返回 WebSocket 地址、子协议与关闭码，便于客户端自配置。
 * </ul>
 */
@RestController
@RequestMapping("/api/client")
public class ClientController {

    private final JwtService jwtService;
    private final SecurityProperties properties;
    private final DeviceIdentityService deviceIdentityService;
    private final BindingService bindingService;
    private final PairingService pairingService;
    private final TurnCredentialService turnCredentialService;
    private final AbuseReportService abuseReportService;
    private final SessionManager sessionManager;

    public ClientController(JwtService jwtService,
                            SecurityProperties properties,
                            DeviceIdentityService deviceIdentityService,
                            BindingService bindingService,
                            PairingService pairingService,
                            TurnCredentialService turnCredentialService,
                            AbuseReportService abuseReportService,
                            SessionManager sessionManager) {
        this.jwtService = jwtService;
        this.properties = properties;
        this.deviceIdentityService = deviceIdentityService;
        this.bindingService = bindingService;
        this.pairingService = pairingService;
        this.turnCredentialService = turnCredentialService;
        this.abuseReportService = abuseReportService;
        this.sessionManager = sessionManager;
    }

    // ==================== 令牌校验 ====================

    /**
     * 校验调用方自身令牌的有效性。
     *
     * <p>客户端在建立 WebSocket 长连前可先调用此接口确认令牌未过期/未失效，
     * 避免握手阶段被直接断开。支持用户令牌与设备令牌。
     *
     * @return valid 是否有效；若有效则附带 principalType、principalId、displayName、
     *         expiresAt（秒级时间戳）、remainingSeconds（剩余有效秒数）。
     */
    @GetMapping("/verify")
    public Map<String, Object> verify(HttpServletRequest request) {
        AuthPrincipal principal = requirePrincipal(request);
        String token = extractBearer(request);

        Map<String, Object> claims;
        try {
            // 不限定用途：用户 access 与设备 device 令牌均可用于 HTTP 鉴权
            claims = jwtService.verify(token, null);
        } catch (AuthException e) {
            Map<String, Object> body = new LinkedHashMap<>();
            body.put("valid", false);
            body.put("error", e.getCode());
            body.put("message", e.getMessage());
            return body;
        }

        long exp = JwtService.claimAsLong(claims, "exp", 0L);
        long now = Instant.now().getEpochSecond();
        long remaining = Math.max(0, exp - now);

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("valid", true);
        body.put("principalType", principal.principalType().name());
        body.put("principalId", principal.principalId());
        body.put("displayName", principal.displayName());
        body.put("expiresAt", exp);
        body.put("remainingSeconds", remaining);
        body.put("serverTime", now);
        return body;
    }

    // ==================== 设备自助信息 ====================

    /**
     * 被控端查看自身信息（需设备令牌）。
     */
    @GetMapping("/device/me")
    public Map<String, Object> deviceMe(HttpServletRequest request) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可访问此接口");
        }
        DeviceAccount device = deviceIdentityService.findByUid(principal.principalId());
        if (device == null) {
            throw AuthException.notFound("设备不存在");
        }

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("deviceUid", device.getDeviceUid());
        body.put("model", device.getModel());
        body.put("status", device.getStatus().name());
        body.put("usable", device.isUsable());
        body.put("provisionedAt", device.getProvisionedAt() == null ? null
                : device.getProvisionedAt().getEpochSecond());
        body.put("lastOnlineAt", device.getLastOnlineAt() == null ? null
                : device.getLastOnlineAt().getEpochSecond());
        if (device.getStatus() == AccountStatus.SUSPENDED && device.getStatusUntil() != null) {
            body.put("statusUntil", device.getStatusUntil().getEpochSecond());
        }
        body.put("statusReason", device.getStatusReason());
        return body;
    }

    // ==================== 握手指引 ====================

    /**
     * 返回客户端建立 WebSocket 长连所需的握手参数，便于各端自配置。
     */
    @GetMapping("/ws-info")
    public Map<String, Object> wsInfo() {
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("wsPath", "/ws/signal");
        body.put("subprotocol", "signal.v1");
        body.put("tokenMethods", new String[]{
                "header:Authorization Bearer <token>",
                "subprotocol:Sec-WebSocket-Protocol: signal.v1, auth.<token>",
                "query:?token=<token>"
        });
        body.put("closeCodes", Map.of(
                "NORMAL", 1000,
                "UNAUTHORIZED", 4001,
                "FORCE_LOGOUT", 4003));
        body.put("idleTimeoutSeconds", 90);
        body.put("recommendedRefreshRatio", 0.33);
        body.put("accessTokenTtlSeconds", properties.getJwt().getAccessTokenTtlSeconds());
        body.put("deviceTokenTtlSeconds", properties.getJwt().getDeviceTokenTtlSeconds());
        body.put("turnEnabled", turnCredentialService.isEnabled());
        return body;
    }

    // ==================== 配对码（建立绑定） ====================

    /**
     * 被控端生成一次性配对码（明文仅回显一次）。主控端输入该码即可建立绑定。
     */
    @PostMapping("/device/pairing-code")
    public Map<String, Object> generatePairingCode(HttpServletRequest request) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可调用");
        }
        String code = pairingService.generate(principal.principalId());
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("code", code);
        r.put("expiresInSeconds", 600);
        return r;
    }

    /**
     * 主控端兑换配对码，建立与被控端的绑定关系。
     */
    @PostMapping("/pairing/redeem")
    public Map<String, Object> redeemPairingCode(HttpServletRequest request, @RequestBody Map<String, String> body) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isUser()) {
            throw AuthException.forbidden("仅用户令牌可调用");
        }
        String code = body.get("code");
        DeviceBinding binding = pairingService.redeem(code, principal.principalId());
        return bindingView(binding);
    }

    // ==================== TURN 短期凭证 ====================

    /**
     * 获取 TURN 短期凭证（ICE servers）。主控端与被控端在建立 PeerConnection 前调用。
     */
    @GetMapping("/turn-credentials")
    public Map<String, Object> turnCredentials(HttpServletRequest request) {
        AuthPrincipal principal = requirePrincipal(request);
        return turnCredentialService.issue(principal.principalType() + ":" + principal.principalId());
    }

    // ==================== 骚扰举报（P2 风控） ====================

    /**
     * 被控端举报某主控端账号骚扰。需提供被举报方用户名。
     */
    @PostMapping("/device/report")
    public Map<String, Object> reportAbuse(HttpServletRequest request, @RequestBody Map<String, String> body) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可调用");
        }
        String username = body.get("username");
        if (username == null || username.isBlank()) {
            throw AuthException.badRequest("被举报用户名不能为空");
        }
        String reportedUserId = bindingService.resolveUserId(username);
        abuseReportService.report(principal.principalId(), reportedUserId,
                body.getOrDefault("reason", ""));
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("ok", true);
        return r;
    }

    // ==================== 被控端自助管理（设备令牌） ====================

    /**
     * 被控端将某主控端账号加入绑定（允许其发起连接）。
     * 通过用户名解析主控端用户 ID，绑定由设备侧创建（OWNER 视为设备所有者）。
     */
    @PostMapping("/device/bind")
    public Map<String, Object> deviceBind(HttpServletRequest request, @RequestBody Map<String, String> body) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可调用");
        }
        String username = body.get("username");
        String alias = body.get("alias");
        String userId = bindingService.resolveUserId(username);
        DeviceBinding binding = bindingService.bind(
                principal.principalId(), userId, DeviceBinding.BindingRole.MEMBER, alias,
                "device:" + principal.principalId());
        return bindingView(binding);
    }

    /** 被控端解除某主控端账号的绑定。 */
    @PostMapping("/device/unbind")
    public Map<String, Object> deviceUnbind(HttpServletRequest request, @RequestBody Map<String, String> body) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可调用");
        }
        String username = body.get("username");
        String userId = bindingService.resolveUserId(username);
        bindingService.revokeBinding(principal.principalId(), userId, "device:" + principal.principalId());
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("ok", true);
        return r;
    }

    /** 被控端拉黑某主控端账号（优先级高于绑定，OFFER 将被服务端拒绝）。 */
    @PostMapping("/device/blacklist")
    public Map<String, Object> deviceBlacklist(HttpServletRequest request, @RequestBody Map<String, String> body) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可调用");
        }
        String username = body.get("username");
        String reason = body.get("reason");
        String userId = bindingService.resolveUserId(username);
        bindingService.addBlacklist(principal.principalId(), userId, reason, "device:" + principal.principalId());
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("ok", true);
        return r;
    }

    /** 被控端解除对某主控端账号的拉黑。 */
    @PostMapping("/device/unblacklist")
    public Map<String, Object> deviceUnblacklist(HttpServletRequest request, @RequestBody Map<String, String> body) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可调用");
        }
        String username = body.get("username");
        String userId = bindingService.resolveUserId(username);
        bindingService.removeBlacklist(principal.principalId(), userId, "device:" + principal.principalId());
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("ok", true);
        return r;
    }

    /** 被控端查看自身的绑定与黑名单列表。 */
    @GetMapping("/device/relations")
    public Map<String, Object> deviceRelations(HttpServletRequest request) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isDevice()) {
            throw AuthException.forbidden("仅设备令牌可调用");
        }
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("bindings", bindingService.listByDevice(principal.principalId()).stream()
                .map(this::bindingView).toList());
        r.put("blacklist", bindingService.listBlacklist(principal.principalId()).stream()
                .map(e -> Map.of(
                        "blockedUserId", e.getBlockedUserId(),
                        "reason", e.getReason() == null ? "" : e.getReason()))
                .toList());
        return r;
    }

    // ==================== 主控端自助查询（用户令牌） ====================

    /** 主控端查看自己已绑定的设备及其在线状态。 */
    @GetMapping("/bindings")
    public Map<String, Object> myBindings(HttpServletRequest request) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isUser()) {
            throw AuthException.forbidden("仅用户令牌可调用");
        }
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("bindings", bindingService.listByUser(principal.principalId()).stream()
                .filter(b -> b.getStatus() == DeviceBinding.BindingStatus.ACTIVE)
                .map(this::bindingView).toList());
        return r;
    }

    /**
     * 主控端查看当前「已绑定且在线」的设备列表，便于快速发起连接。
     * 仅返回当前在信令网络中有活跃 WebSocket 会话的被控端。
     */
    @GetMapping("/devices/online")
    public Map<String, Object> onlineDevices(HttpServletRequest request) {
        AuthPrincipal principal = requirePrincipal(request);
        if (!principal.isUser()) {
            throw AuthException.forbidden("仅用户令牌可调用");
        }
        List<Map<String, Object>> online = bindingService.listByUser(principal.principalId()).stream()
                .filter(b -> b.getStatus() == DeviceBinding.BindingStatus.ACTIVE)
                .filter(b -> sessionManager.isDeviceOnline(b.getDeviceUid()))
                .map(b -> {
                    Map<String, Object> m = new LinkedHashMap<>();
                    m.put("deviceUid", b.getDeviceUid());
                    m.put("alias", b.getAlias());
                    m.put("online", true);
                    return m;
                })
                .toList();
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("devices", online);
        return r;
    }

    private Map<String, Object> bindingView(DeviceBinding b) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("bindingId", b.getBindingId());
        m.put("deviceUid", b.getDeviceUid());
        m.put("userId", b.getUserId());
        m.put("role", b.getRole().name());
        m.put("alias", b.getAlias());
        m.put("status", b.getStatus().name());
        m.put("online", sessionManager.isDeviceOnline(b.getDeviceUid()));
        return m;
    }

    // ==================== 工具方法 ====================

    private AuthPrincipal requirePrincipal(HttpServletRequest request) {
        AuthPrincipal principal = (AuthPrincipal) request.getAttribute(BearerAuthFilter.ATTR_PRINCIPAL);
        if (principal == null) {
            throw AuthException.unauthorized("令牌无效或缺失");
        }
        return principal;
    }

    private String extractBearer(HttpServletRequest request) {
        String authorization = request.getHeader("Authorization");
        if (authorization != null && authorization.regionMatches(true, 0, "Bearer ", 0, 7)) {
            String token = authorization.substring(7).trim();
            return token.isEmpty() ? null : token;
        }
        return null;
    }
}
