"""CPS 群发短链落地:用户点 /c/{code} → 记点击 → 按平台 302 跳 或 返回淘宝落地页。

公网无鉴权(群里任何人点都要能跳/能领)。记点击失败绝不影响用户。
- 美团/京东:记 visit + 302 跳 target(美团短链 / 京东链接)
- 淘宝:记 visit + 返回 H5 落地页(整段淘口令复制按钮);点"复制口令"→ POST /c/{code}/copy 记 copy
"""
from __future__ import annotations

import html
import json
import logging

from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse
from sqlalchemy.orm import Session

from app.core import media
from app.core.config import settings
from app.db.session import get_db
from app.integrations import wx_oauth
from app.models.cps_activity import CpsActivity
from app.models.cps_link import CpsLink
from app.repositories import cps_link as cps_link_repo
from app.repositories import cps_wx_user as cps_wx_user_repo

logger = logging.getLogger("shagua.cps_redirect")

router = APIRouter(tags=["cps-redirect"])

# code 不存在/失效时的兜底落地
_FALLBACK_URL = "https://www.meituan.com/"

# 淘宝活动未设图时的兜底主视觉(存量已回填,基本只在老 link/异常时触发)
_DEFAULT_TAOBAO_IMAGE = "/media/taobao_landing.jpg"

# 微信网页授权拿到的用户标识 cookie(种在 coupon 域,30 天免重复授权)
_WX_OPENID_COOKIE = "wx_openid"
_WX_UINFO_COOKIE = "wx_uinfo"  # "1" = 已拿过昵称头像(userinfo),点领券不再跳授权
_COOKIE_MAX_AGE = 30 * 86400


def _is_wechat(request: Request) -> bool:
    """是否微信内置浏览器(网页授权只在微信内有意义,外部浏览器不跳授权)。"""
    return "micromessenger" in (request.headers.get("user-agent", "").lower())


def _client_ip(request: Request) -> str | None:
    xff = request.headers.get("x-forwarded-for")
    if xff:
        return xff.split(",")[0].strip()
    return request.client.host if request.client else None


def _record(
    db: Session, link: CpsLink, request: Request, event_type: str, openid: str | None = None
) -> None:
    try:
        cps_link_repo.record_click(
            db, link=link, ip=_client_ip(request),
            ua=request.headers.get("user-agent"), event_type=event_type, openid=openid,
        )
    except Exception:
        pass  # 记点击失败不阻断


@router.get("/MP_verify_F7wnRQ7xPbhVOWC8.txt", include_in_schema=False)
def wx_mp_domain_verify() -> PlainTextResponse:
    """微信「网页授权域名」归属校验文件。coupon.shaguabijia.com 全反代 app-server,
    微信请求 coupon.shaguabijia.com/MP_verify_xxx.txt 验证域名归属;内容=文件名核心串。
    放域名根目录(本端点即根路径),为微信网页授权(拿 openid)接入做准备。"""
    return PlainTextResponse("F7wnRQ7xPbhVOWC8")


@router.get("/c/{code}", summary="群发短链落地(微信授权拿 openid + 记点击 + 跳转/淘宝落地页)")
def cps_landing(code: str, request: Request, db: Session = Depends(get_db)):
    link = cps_link_repo.get_by_code(db, code)
    if link is None:
        return RedirectResponse(_FALLBACK_URL, status_code=302)
    openid = request.cookies.get(_WX_OPENID_COOKIE)
    # 微信内 + 还没拿到 openid + 配了服务号 → 先 base 静默授权拿 openid,再 302 回本页。
    # 非微信 / 已有 cookie / 未配服务号 → 直接走原逻辑(openid 可能为 None,绝不阻断领券)。
    if openid is None and settings.wx_oauth_active and _is_wechat(request):
        redirect_uri = f"{settings.CPS_REDIRECT_BASE.rstrip('/')}/wx/oauth/cb"
        auth_url = wx_oauth.build_authorize_url(redirect_uri, "snsapi_base", f"base:{code}")
        return RedirectResponse(auth_url, status_code=302)
    # ?authed=1 是 userinfo 授权后的自动回跳重载,不是用户主动打开:visit 已在授权前那次
    # 落地(base 静默授权回来那次)记过,这里再记会让每个走"领券授权"的用户 visit 虚增 1。
    if request.query_params.get("authed") != "1":
        _record(db, link, request, "visit", openid=openid)
    if link.platform == "taobao":
        activity = db.get(CpsActivity, link.activity_id)
        image_url = (activity.image_url if activity else None) or _DEFAULT_TAOBAO_IMAGE
        has_uinfo = request.cookies.get(_WX_UINFO_COOKIE) == "1"
        return HTMLResponse(
            _taobao_landing_html(link.target_url, image_url, code, openid, has_uinfo)
        )
    # 美团短链 / 京东链接:微信内 + 已拿 openid + 还没授权过头像 → 先出「领券前授权」插页,
    # 点按钮(交互手势,避免微信快照页)走 snsapi_userinfo 拿昵称头像,授权回来经
    # /c/{code}?authed=1 自动 302 跳券。其余(非微信 / 未配授权 / 无 openid / 已授权)直接 302。
    # 产品决定:每次未授权都提示(不做冷却、不种"已提示"cookie)。取消授权由插页前端自动
    # 转跳目标券页(无单独"暂不授权"出口),拒绝也能拿到券(对齐「绝不阻断领券」)。
    if (
        settings.wx_oauth_active
        and _is_wechat(request)
        and openid is not None
        and request.cookies.get(_WX_UINFO_COOKIE) != "1"
    ):
        return HTMLResponse(_coupon_auth_landing_html(link.target_url, code))
    return RedirectResponse(link.target_url, status_code=302)


@router.get("/wx/oauth/cb", include_in_schema=False)
def wx_oauth_cb(
    code: str | None = None, state: str | None = None, db: Session = Depends(get_db)
):
    """微信网页授权回调。state='base:{原code}'(静默拿 openid) 或 'uinfo:{原code}'(补昵称头像)。
    换 openid(+userinfo)→ upsert 用户 → 种 cookie → 302 回落地页。任何失败兜底回落地页,
    绝不阻断用户领券。"""
    kind, _, orig_code = (state or "").partition(":")
    # 授权弹窗点「取消」时,部分微信版本会带空 code 回调本端点(而非退回落地页)。无 code 换
    # 不到 openid,直接兜底跳回落地页(美团/京东落地页前端会再转跳目标券页),不报 422。
    if not code:
        return RedirectResponse(
            f"/c/{orig_code}" if orig_code else _FALLBACK_URL, status_code=302
        )
    try:
        token = wx_oauth.exchange_code(code)  # {openid, access_token, scope, ...}
        openid = token["openid"]
        link = cps_link_repo.get_by_code(db, orig_code)
        group_id = link.group_id if link else None
        nickname = headimgurl = unionid = None
        if kind == "uinfo" and "userinfo" in (token.get("scope") or ""):
            info = wx_oauth.get_userinfo(token["access_token"], openid)
            nickname, headimgurl, unionid = (
                info.get("nickname"), info.get("headimgurl"), info.get("unionid"),
            )
        cps_wx_user_repo.upsert(
            db, openid=openid, code=orig_code, group_id=group_id,
            nickname=nickname, headimgurl=headimgurl, unionid=unionid,
        )
    except Exception:
        logger.exception("[wx_oauth] callback failed state=%s", state)
        return RedirectResponse(f"/c/{orig_code}" if orig_code else _FALLBACK_URL, status_code=302)
    # userinfo 授权回来带 ?authed=1,落地页据此自动触发复制(把"点领券→授权→复制"衔接为一步)
    target = f"/c/{orig_code}" + ("?authed=1" if kind == "uinfo" else "")
    resp = RedirectResponse(target, status_code=302)
    resp.set_cookie(_WX_OPENID_COOKIE, openid, max_age=_COOKIE_MAX_AGE, httponly=True, samesite="lax")
    if nickname:
        resp.set_cookie(_WX_UINFO_COOKIE, "1", max_age=_COOKIE_MAX_AGE, samesite="lax")
    return resp


@router.post("/c/{code}/copy", summary="淘宝落地页点了「复制口令」(记 copy 事件,带 openid)")
def cps_copy(code: str, request: Request, db: Session = Depends(get_db)) -> dict:
    link = cps_link_repo.get_by_code(db, code)
    if link is not None:
        _record(db, link, request, "copy", openid=request.cookies.get(_WX_OPENID_COOKIE))
    return {"ok": True}


def _js_str(s: str) -> str:
    """把字符串安全嵌进 <script> 的 JS 字面量:json.dumps 不转义 '<',值里含 '</script>'
    会逃逸标签 → 把 '<' 转成 \\u003c(JS 仍解析为 '<')。auth/target/淘口令等都走它。"""
    return json.dumps(s).replace("<", "\\u003c")


def _taobao_landing_html(
    token: str, image_url: str, code: str, openid: str | None, has_uinfo: bool
) -> str:
    """淘宝落地页 H5(主视觉图按活动传入,复制淘口令按钮在 75% 屏高)。

    image_url 经 html.escape 嵌入 <img src>(防注入);token 经 json.dumps 安全嵌入 JS。
    uinfo_url:已有 openid 但还没拿过昵称头像时,生成 userinfo 授权链接 —— 用户点「领券」
    时先跳它(交互触发,避免微信快照页),授权回来自动复制。已拿过 / 无 openid 则为空,直接复制。
    """
    safe_img = html.escape(media.to_abs_media_url(image_url) or image_url, quote=True)
    uinfo_url = ""
    if openid and not has_uinfo and settings.wx_oauth_active:
        redirect_uri = f"{settings.CPS_REDIRECT_BASE.rstrip('/')}/wx/oauth/cb"
        uinfo_url = wx_oauth.build_authorize_url(redirect_uri, "snsapi_userinfo", f"uinfo:{code}")
    return (
        _TAOBAO_HTML
        .replace("__IMAGE_URL__", safe_img)
        .replace("__UINFO_URL__", _js_str(uinfo_url))
        .replace("__TOKEN_JS__", _js_str(token))
    )


_TAOBAO_HTML = """<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>淘宝闪购 · 天天领红包</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{font-family:-apple-system,"PingFang SC",sans-serif;background:#fff0ef;color:#333;min-height:100vh}
.hero{display:block;width:100%}
.btn-wrap{position:fixed;left:0;right:0;top:75vh;padding:0 20px}
.btn{display:block;width:100%;background:linear-gradient(90deg,#ff5b5b,#ff3b3b);color:#fff;font-size:19px;font-weight:800;text-align:center;padding:16px;border:none;border-radius:30px;box-shadow:0 6px 16px rgba(255,59,59,.4)}
.btn:active{transform:scale(.98)}
.toast{position:fixed;left:50%;top:42%;transform:translate(-50%,-50%);background:rgba(0,0,0,.82);color:#fff;padding:12px 22px;border-radius:10px;font-size:15px;opacity:0;transition:opacity .25s;pointer-events:none;z-index:99;white-space:nowrap}
.toast.show{opacity:1}
</style>
</head>
<body>
<img class="hero" src="__IMAGE_URL__" alt="淘宝闪购 天天领红包">
<div class="btn-wrap"><button class="btn" onclick="copyToken()">复制口令去淘宝领红包</button></div>
<div class="toast" id="toast"></div>
<script>
var TOKEN = __TOKEN_JS__;
var UINFO_URL = __UINFO_URL__;  // 非空=还没拿昵称头像,点领券先跳它补(授权回来自动复制)
function showToast(m){var t=document.getElementById('toast');t.textContent=m;t.classList.add('show');setTimeout(function(){t.classList.remove('show')},2200)}
function reportCopy(){try{fetch(location.pathname+'/copy',{method:'POST',keepalive:true})}catch(e){}}
function done(){showToast('复制成功!打开淘宝即可领取');reportCopy()}
function fallback(){
  var ta=document.createElement('textarea');ta.value=TOKEN;ta.style.position='fixed';ta.style.opacity='0';
  document.body.appendChild(ta);ta.focus();ta.select();
  try{document.execCommand('copy');done()}catch(e){showToast('复制失败,请长按手动复制')}
  document.body.removeChild(ta);
}
function doCopy(){
  if(navigator.clipboard&&window.isSecureContext){navigator.clipboard.writeText(TOKEN).then(done).catch(fallback)}
  else{fallback()}
}
function copyToken(){
  // 第一次领券且还没授权过昵称头像:先跳 userinfo 授权(用户点击=交互触发,避免快照页),
  // 授权回来 ?authed=1 自动复制;已授权过/无 openid 则直接复制。
  if(UINFO_URL){location.href=UINFO_URL;return}
  doCopy();
}
// userinfo 授权回流(?authed=1):自动复制,把"点领券→授权→复制"衔接成一步无感;
// 复制后用 replaceState 清掉 authed=1,避免刷新/微信返回(BFCache)重载时重复触发复制上报。
if(location.search.indexOf('authed=1')>=0){doCopy();history.replaceState(null,'',location.pathname)}
</script>
</body>
</html>"""


def _coupon_auth_landing_html(target_url: str, code: str) -> str:
    """美团/京东「领券前先授权头像」插页(单按钮)。

    唯一按钮「立即领取优惠券」→ snsapi_userinfo 授权(用户点击=交互手势,避免微信快照页)。
    - 点「允许」→ 回调拿昵称头像 + 种 wx_uinfo cookie → 经 /c/{code}?authed=1 自动 302 跳券。
    - 点「取消」→ 微信退回本插页,前端 pageshow/visibilitychange 检测到"刚点过领取"即直接
      转跳 target(不再设单独的"暂不授权"出口),保证拒绝也能拿到券(对齐「绝不阻断领券」)。
    AUTH/TARGET 经 json.dumps 安全嵌入 JS 字符串(URL 里的 & 不会被 HTML 转义,直跳更稳)。
    """
    redirect_uri = f"{settings.CPS_REDIRECT_BASE.rstrip('/')}/wx/oauth/cb"
    auth_url = wx_oauth.build_authorize_url(redirect_uri, "snsapi_userinfo", f"uinfo:{code}")
    return (
        _COUPON_AUTH_HTML
        .replace("__AUTH_URL__", _js_str(auth_url))
        .replace("__TARGET_URL__", _js_str(target_url))
    )


_COUPON_AUTH_HTML = """<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>领取优惠券</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{font-family:-apple-system,"PingFang SC",sans-serif;background:#fff0ef;color:#333;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.card{width:100%;max-width:340px;background:#fff;border-radius:18px;padding:34px 24px;text-align:center;box-shadow:0 8px 24px rgba(255,59,59,.12)}
.title{font-size:22px;font-weight:800;color:#ff3b3b;margin-bottom:10px}
.sub{font-size:14px;color:#999;line-height:1.6;margin-bottom:28px}
.btn{display:block;width:100%;background:linear-gradient(90deg,#ff5b5b,#ff3b3b);color:#fff;font-size:18px;font-weight:800;font-family:inherit;text-align:center;padding:15px;border:none;border-radius:28px;cursor:pointer;box-shadow:0 6px 16px rgba(255,59,59,.4)}
.btn:active{transform:scale(.98)}
</style>
</head>
<body>
<div class="card">
<div class="title">🎁 优惠券待领取</div>
<div class="sub">点击下方按钮，领取你的专属优惠券</div>
<button class="btn" onclick="claim()">立即领取优惠券</button>
</div>
<script>
var AUTH=__AUTH_URL__,TARGET=__TARGET_URL__;
function claim(){
  try{sessionStorage.setItem('mt_auth_ts',String(Date.now()))}catch(e){}
  location.href=AUTH;
}
// 从授权页返回(点了「取消」/未完成授权):若刚点过领取(3 分钟内)→ 直接进目标券页,
// 保证拒绝也能拿到券(绝不阻断领券)。pageshow 覆盖 BFCache 与整页重载,visibilitychange
// 兜底"页面保活只切可见性"的情况;靠 sessionStorage 时间戳防陈旧/误触发。
function maybeForward(){
  var ts=0;try{ts=parseInt(sessionStorage.getItem('mt_auth_ts')||'0',10)}catch(e){}
  if(ts&&Date.now()-ts<180000){
    try{sessionStorage.removeItem('mt_auth_ts')}catch(e){}
    location.href=TARGET;
  }
}
window.addEventListener('pageshow',maybeForward);
document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible')maybeForward()});
</script>
</body>
</html>"""
