
    i                        d Z ddlmZ ddlZddlZddlZddlmZmZmZ ddl	m
Z
 ddlmZmZ ddlmZ ddlmZ dd	lmZ  ej*                  d
      ZdZ ej0                  dej2                        ZddZddZddZ G d de      ZddZ ddZ!y)uY  
LINE ↔ Cooper 帳號綁定流程(階段 6b)。

API 對外:
  - generate_code(user)        : 在 DB 產一筆 6 位數綁定碼,TTL 10 分鐘
  - redeem_code(code, line_user_id, src_db_user)
                                : 把 LINE shadow user 的 line_user_id 搬到網頁 user
                                  shadow user 軟刪;訊息歸屬全搬到網頁 user
  - parse_bind_command(text)   : 從 LINE 訊息抓「綁定 482593」/ 「/bind 482593」格式

設計重點:
  - 重產的 code 不會 invalidate 之前的 code(那筆只要還沒 used + 沒過期就能用)
  - redeem 內整段在同一 transaction(避免半搬)
  - 若 web user 已有 line_user_id → 覆蓋;原 line_user_id 解除(Cooper 不會綁兩個帳號)
  - 若 LINE shadow user 不存在(user 還沒對 Cooper 講過話)→ redeem 失敗,提示「先傳一句話再綁」
    )annotationsN)datetime	timedeltatimezone)UUID)selectupdate)Message)LineBindingCode)Userzcoba.line.binding
   u;   ^(?:/?bind|綁定|綁定碼|繫結)\s*[:：]?\s*(\d{6})\s*$c                v    | syt         j                  | j                               }|sy|j                  d      S )u4   從訊息抓 6 位數綁定碼;沒 match 回 None。N   )_BIND_REmatchstripgroup)textms     =/volume1/homes/robertsu/coba/app/app/services/line_binding.pyparse_bind_commandr   /   s0    tzz|$A771:    c                 0    t        j                  d      dS )u4   6 位數,allow leading zero(secrets 取 0~999999)。i@B 06d)secrets	randbelow r   r   	_gen_coder   9   s    	*3/0r   c           	       K   t        j                  t        j                        }|t	        t
              z   }d}t        d      D ]  }t               }| j                  t        t              j                  t        j                  |k(  t        j                  j                  d      t        j                  |kD               d{   j!                         }|t        |||||      }| j#                  |       	 | j%                          d{    |c S  |r|t+        d      7 c7 # t&        $ r&}	|	}| j)                          d{  7   Y d}	~	d}	~	ww xY ww)u   產一張新的綁定碼。
    - 若 6 位數隨機撞到既存 unused & not-expired,自動 retry 最多 5 次
      (10^6 空間,撞機率近 0)
    - 接 UUID 而非 User 物件,避免跨 session lazy-load 失敗
    )minutesN   )codeuser_id
expires_at
created_at
updated_atu   產綁定碼失敗)r   nowr   utcr   CODE_TTL_MINUTESranger   executer   r   wherer"   used_atis_r$   scalar_one_or_noneaddflush	ExceptionrollbackRuntimeError)
dbr#   r'   r$   last_err_r"   existingreces
             r   generate_coder;   >   s;     ,,x||
$Cy)9::J!%H1X{**?#))$$,''++D1**S0
 
  	 !
 	s	((*
 
5 6 
+
,,7
&  	H++-	sZ   CED(6E<D,D*D,E*D,,	E5E
EEEEEc                      e Zd ZdZy)BindingErroru2   redeem 失敗時用,訊息直接給使用者看。N)__name__
__module____qualname____doc__r   r   r   r=   r=   g   s    <r   r=   c                  K   t        j                  t        j                        }| j	                  t        t              j                  t        j                  |k(               d{   j                         }|t        d      |j                  t        d      |j                  }|j                   |j                  t        j                        }||k  rt        d      | j	                  t        t              j                  t        j                   |j"                  k(  t        j$                  j'                  d                   d{   j                         }|t        d      | j	                  t        t              j                  t        j(                  |k(  t        j$                  j'                  d                   d{   j                         }|b|j(                  r.|j(                  |k7  rd|_        | j+                          d{    ||_        ||_        | j+                          d{    |S |j                   |j                   k(  r!||_        | j+                          d{    |S |j,                  rt        d      | j	                  t/        t0              j                  t0        j"                  |j                   k(        j3                  |j                                d{   }|j4                  xs d	}	d|_        ||_        d
|_        | j+                          d{    |j(                  r;|j(                  |k7  r,t8        j;                  d|j                   |j(                  |       ||_        ||_        | j+                          d{    t8        j;                  d|j                   ||j                   |	       |S 7 r7 7 7 7 7 p7 7 7 Iw)u]  把 line_user_id 綁到 code.user_id 上,回傳綁好的 web user。

    這個函式會跑這幾件事(同 transaction):
      1. 確認 code 存在 + 沒過期 + 沒 used
      2. 找 web user(目標)
      3. 找 LINE shadow user(目前 line_user_id 對應的)
      4. 如果 shadow == web user → 早就綁好,no-op 成功
      5. 否則:
         - 把 shadow 的所有 message.user_id 搬到 web user
         - 清掉 web user 既有 line_user_id(萬一已綁別的)+ 設成新的
         - shadow user 軟刪 + 把 line_user_id=NULL(避免 unique constraint)
      6. 標記 code.used_at
    Nu.   綁定碼不存在,去網頁版重新產一個u7   這個綁定碼已經用過了,去網頁版重產一個)tzinfou6   綁定碼過期了(10 分鐘),去網頁版重產一個u9   綁定碼對應的網頁帳號不存在(可能被砍了?)ug   你的 LINE 已經綁定到另一個帳號。請先到那個帳號解除綁定,再用新碼綁過來。)r#   r   Fu'   web user %s 原已綁 LINE %s,改綁 %suA   LINE binding 完成 user=%s line_id=%s shadow=%s 搬訊息 %d 則)r   r'   r   r(   r+   r   r   r,   r"   r/   r=   r-   r$   rC   replacer   idr#   
deleted_atr.   line_user_idr1   password_hash
sql_updater
   valuesrowcount	is_activeloggerinfo)
r5   r"   rG   r'   r9   r$   web_usershadowmovedmoved_counts
             r   redeem_coderS   k   s@     ,,x||
$C%%o&:&:d&BC   {KLL
{{TUUJ ''x||'<
SSTTjjt477ckk14??3F3Ft3LM   VWWJJt4,,<doo>Q>QRV>WX   ~   X%:%:l%J$(H!((* ,hhjyyHKKhhj
 u
 	
 **7	w&))+	,		$ E
 ..%AK FFF
((* !6!6,!F=x{{HLaLacop(H CK
((*
KKK\699k O_  	 	  s   A%O1'O(C'O1OA>O1OA	O1O"%O1>O%?9O18O(9BO1;O+<<O18O-9A,O1%O/&4O1O1O1"O1%O1(O1+O1-O1/O1c                  K   | j                  t        t              j                  t        j                  |k(  t        j
                  j                  d                   d{   j                         }||j                  syt        j                  d|j                  |j                         d|_        | j                          d{    y7 l7 w)u:  解除某個 web user 的 LINE 綁定。

    清掉 user.line_user_id 即可。「過去的訊息歸屬」不變動(已經 reassign 過,屬於 web user)。
    日後若同一個 LINE userId 再來訊息,會被當成新人,自動建影子帳號。
    回傳:True = 有解綁成功;False = 原本就沒綁。
    NFu.   LINE unbind: user=%s 解除原 line_user_id=%sT)r+   r   r   r,   rE   rF   r.   r/   rG   rM   rN   r1   )r5   r#   us      r   unbindrV      s      zzt477g-t/B/B4/HI 
  	y
KK@!$$WAN
((*
 s%   A C"C#A&C	C
CC)r   strreturnz
str | None)rX   rW   )r#   r   rX   r   )r"   rW   rG   rW   rX   r   )r#   r   rX   bool)"rA   
__future__r   loggingrer   r   r   r   uuidr   
sqlalchemyr   r	   rI   app.models.channelr
   app.models.line_bindingr   app.models.userr   	getLoggerrM   r)   compile
IGNORECASEr   r   r   r;   r2   r=   rS   rV   r   r   r   <module>re      s   " #  	  2 2  3 & 3   
		.	/  
 2::BMM1
&-R=9 =^Br   