<template>
  <div class="help-content">
    <div class="content-header">
      <h2>快速对接指南 (Redirect SSO)</h2>
      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/redirect_sso.md', 'Redirect_SSO_Guide.md')">
        <el-icon style="margin-right: 5px"><Download /></el-icon>
        下载 开发文档
      </el-button>
    </div>
    <p class="intro">
      这是最简单、最快速的集成方式。您无需开发前端登录页面，只需将用户重定向到统一认证平台，待用户登录后，平台会将携带票据 (Ticket) 的用户重定向回您的系统。
    </p>

    <div class="section">
      <h3>1. 核心流程</h3>
      <div class="flow-chart">
         <div class="step">
          <div class="self-action" style="margin-left: 0; width: auto; display: inline-block;">1. 用户访问应用 (未登录)</div>
          <div class="arrow client-to-uap" style="position: relative; top: 0; left: 10px; width: 50px;"></div>
          <div class="self-action" style="margin-left: 10px; width: auto; display: inline-block;">2. 跳转 UAP 登录 (?app_id=xxx)</div>
         </div>
         <div class="step" style="margin-top: 10px;">
          <div class="self-action" style="margin-left: 0; width: auto; display: inline-block;">3. 用户完成登录</div>
          <div class="arrow uap-to-client" style="position: relative; top: 0; left: 10px; width: 50px;"></div>
          <div class="self-action" style="margin-left: 10px; width: auto; display: inline-block;">4. 回调应用 (?ticket=xxx)</div>
         </div>
          <div class="step" style="margin-top: 10px;">
          <div class="self-action" style="margin-left: 0; width: auto; display: inline-block;">5. 应用后端验证 Ticket</div>
          <div class="arrow client-to-user" style="position: relative; top: 0; left: 10px; width: 50px; border-style: dashed;"></div>
          <div class="self-action" style="margin-left: 10px; width: auto; display: inline-block;">6. 登录成功</div>
         </div>
      </div>
    </div>

    <div class="section">
      <h3>2. 详细集成步骤</h3>
      
      <h4>第一步：准备工作</h4>
      <ul>
          <li>在 UAP 管理后台创建应用。</li>
          <li><strong>关键：</strong> 在应用配置中填写入合法的 <strong>回调地址 (Redirect URIs)</strong>。例如：<code>http://your-app.com/callback</code></li>
          <li>获取 <code>App ID</code> 和 <code>App Secret</code> (用于后端验证 Ticket)。</li>
      </ul>

      <h4>第二步：拼接登录链接 (前端)</h4>
      <p>在您的应用中，检测到用户未登录时，直接跳转到以下地址：</p>
      
      <div class="code-block">
          <pre>
# PC 端
{{ currentOrigin }}/login?app_id=YOUR_APP_ID

# 移动端 (H5)
{{ currentOrigin }}/mobile/login?app_id=YOUR_APP_ID
          </pre>
      </div>
       <el-alert title="提示" type="info" :closable="false" show-icon>
          <div>
              请将 <code>YOUR_APP_ID</code> 替换为您实际的应用 ID。<br>
              支持参数名 <code>app_id</code> 或 <code>appid</code>。
          </div>
      </el-alert>

      <h4>第三步：实现回调接口 (后端)</h4>
      <p>用户登录成功后，浏览器会跳转到您配置的回调地址，URL 格式如下：</p>
      <code>http://your-app.com/callback?ticket=TICKET-xxxxx</code>
      
      <p>您的后端需要接收 <code>ticket</code>，并调用 UAP 的验证接口换取用户信息：</p>
      
      <el-tabs type="border-card" class="code-tabs">
        <el-tab-pane label="Python">
          <div class="code-block">
            <pre>
import requests
import time
import hmac
import hashlib

def generate_signature(secret: str, params: dict) -> str:
    # 1. 过滤掉空值和 sign 字段
    data = {k: v for k, v in params.items() if k != "sign" and v is not None}
    # 2. 排序 key
    sorted_keys = sorted(data.keys())
    # 3. 拼接字符串
    query_string = "&".join([f"{k}={data[k]}" for k in sorted_keys])
    # 4. 计算 HMAC-SHA256
    signature = hmac.new(
        secret.encode('utf-8'),
        query_string.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return signature

def validate_ticket(ticket):
    # API 地址通常为 /api/v1/simple/validate
    url = "http://your-uap-domain/api/v1/simple/validate"
    app_secret = "YOUR_APP_SECRET" # 务必保密

    payload = {
        "app_id": "YOUR_APP_ID",
        "ticket": ticket,
        "timestamp": int(time.time())
    }
    # 使用您的 App Secret 计算签名
    payload["sign"] = generate_signature(app_secret, payload)
    
    try:
        resp = requests.post(url, json=payload)
        return resp.json()
    except Exception as e:
        print("Error:", e)
        return None
            </pre>
          </div>
        </el-tab-pane>

        <el-tab-pane label="Java">
          <div class="code-block">
            <pre>
// 依赖建议: OkHttp 或 Apache HttpClient, FastJson/Jackson
import okhttp3.*;
import com.alibaba.fastjson.JSON;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class TicketValidator {
    public static final String APP_ID = "YOUR_APP_ID";
    public static final String APP_SECRET = "YOUR_APP_SECRET";
    public static final String API_URL = "http://your-uap-domain/api/v1/simple/validate";

    public static String generateSign(Map&lt;String, String&gt; params, String secret) {
        try {
            List&lt;String&gt; sortedKeys = new ArrayList&lt;&gt;(params.keySet());
            Collections.sort(sortedKeys);
            
            StringBuilder sb = new StringBuilder();
            for (String key : sortedKeys) {
                if (!key.equals("sign") && params.get(key) != null) {
                    if (sb.length() > 0) sb.append("&");
                    sb.append(key).append("=").append(params.get(key));
                }
            }
            
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            
            byte[] bytes = sha256_HMAC.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8));
            StringBuilder hex = new StringBuilder();
            for (byte b : bytes) hex.append(String.format("%02x", b));
            return hex.toString();
        } catch (Exception e) {
            return "";
        }
    }

    public static void validate(String ticket) {
        try {
            long timestamp = System.currentTimeMillis() / 1000;
            
            Map&lt;String, String&gt; params = new HashMap&lt;&gt;();
            params.put("app_id", APP_ID);
            params.put("ticket", ticket);
            params.put("timestamp", String.valueOf(timestamp));
            
            String sign = generateSign(params, APP_SECRET); 
            params.put("sign", sign);
            
            OkHttpClient client = new OkHttpClient();
            RequestBody body = RequestBody.create(
                MediaType.parse("application/json; charset=utf-8"), 
                JSON.toJSONString(params)
            );
            
            Request request = new Request.Builder()
                .url(API_URL)
                .post(body)
                .build();
                
            Response response = client.newCall(request).execute();
            System.out.println(response.body().string());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
            </pre>
          </div>
        </el-tab-pane>

        <el-tab-pane label="Kotlin">
          <div class="code-block">
            <pre>
import okhttp3.*
import com.google.gson.Gson
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.io.IOException
import java.util.*

object AuthUtils {
    fun generateSign(params: Map&lt;String, String&gt;, secret: String): String {
        val sortedKeys = params.keys.filter { it != "sign" }.sorted()
        val queryString = sortedKeys.joinToString("&") { "${it}=${params[it]}" }
        
        val hmacSha256 = "HmacSHA256"
        val secretKeySpec = SecretKeySpec(secret.toByteArray(Charsets.UTF_8), hmacSha256)
        val mac = Mac.getInstance(hmacSha256)
        mac.init(secretKeySpec)
        
        return mac.doFinal(queryString.toByteArray(Charsets.UTF_8))
            .joinToString("") { "%02x".format(it) }
    }
}

fun validateTicket(ticket: String) {
    val appId = "YOUR_APP_ID"
    val appSecret = "YOUR_APP_SECRET"
    val url = "http://your-uap-domain/api/v1/simple/validate"
    
    val timestamp = System.currentTimeMillis() / 1000
    val params = mutableMapOf(
        "app_id" to appId,
        "ticket" to ticket,
        "timestamp" to timestamp.toString()
    )
    
    params["sign"] = AuthUtils.generateSign(params, appSecret)
    
    val client = OkHttpClient()
    val jsonBody = Gson().toJson(params)
    val body = RequestBody.create(MediaType.parse("application/json"), jsonBody)
    
    val request = Request.Builder()
        .url(url)
        .post(body)
        .build()
        
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            e.printStackTrace()
        }

        override fun onResponse(call: Call, response: Response) {
            println(response.body()?.string())
        }
    })
}
            </pre>
          </div>
        </el-tab-pane>
        
        <el-tab-pane label="JavaScript (Node.js)">
          <div class="code-block">
            <pre>
const axios = require('axios');
const crypto = require('crypto');

const APP_SECRET = 'YOUR_APP_SECRET'; // 务必保密

function getSign(params) {
  const keys = Object.keys(params)
    .filter(k => k !== 'sign' && params[k] !== undefined)
    .sort();
  const queryString = keys.map(k => `${k}=${params[k]}`).join('&');
  return crypto.createHmac('sha256', APP_SECRET)
    .update(queryString)
    .digest('hex');
}

async function validateTicket(ticket) {
  const url = 'http://your-uap-domain/api/v1/simple/validate';
  const payload = {
    app_id: 'YOUR_APP_ID',
    ticket: ticket,
    timestamp: Math.floor(Date.now() / 1000)
  };
  
  // 计算签名
  payload.sign = getSign(payload);
  
  try {
    const res = await axios.post(url, payload);
    console.log('Validation Result:', res.data);
    return res.data;
  } catch (error) {
    console.error('Validation Failed:', error.response?.data || error.message);
  }
}
            </pre>
          </div>
        </el-tab-pane>
        
        <el-tab-pane label="Go">
          <div class="code-block">
            <pre>
package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "net/http"
    "sort"
    "strings"
    "time"
)

func GetSign(secret string, params map[string]interface{}) string {
    var keys []string
    for k := range params {
        if k != "sign" {
            keys = append(keys, k)
        }
    }
    sort.Strings(keys)
    var parts []string
    for _, k := range keys {
        val := fmt.Sprintf("%v", params[k])
        parts = append(parts, fmt.Sprintf("%s=%s", k, val))
    }
    query := strings.Join(parts, "&")
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(query))
    return hex.EncodeToString(h.Sum(nil))
}

func ValidateTicket(ticket string) {
    appId := "YOUR_APP_ID"
    appSecret := "YOUR_APP_SECRET" 
    apiUrl := "http://your-uap-domain/api/v1/simple/validate"
    
    params := map[string]interface{}{
        "app_id":    appId,
        "ticket":    ticket,
        "timestamp": time.Now().Unix(),
    }
    
    // 计算签名
    params["sign"] = GetSign(appSecret, params)
    
    jsonData, _ := json.Marshal(params)
    resp, err := http.Post(apiUrl, "application/json", bytes.NewBuffer(jsonData))
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()
    
    // 读取响应...
    var result map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&result)
    fmt.Println("Result:", result)
}
            </pre>
          </div>
        </el-tab-pane>
        
        <el-tab-pane label="Swift">
          <div class="code-block">
            <pre>
import Foundation
import CommonCrypto

// 注意：需引入 CommonCrypto

func hmac(string: String, key: String) -> String {
    var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), key, key.count, string, string.count, &digest)
    let data = Data(digest)
    return data.map { String(format: "%02hhx", $0) }.joined()
}

func generateSign(secret: String, params: [String: Any]) -> String {
    let sortedKeys = params.keys.filter { $0 != "sign" }.sorted()
    let queryParts = sortedKeys.map { key in
        return "\(key)=\(params[key]!)"
    }
    let queryString = queryParts.joined(separator: "&")
    return hmac(string: queryString, key: secret)
}

func validateTicket(ticket: String) {
    let appId = "YOUR_APP_ID"
    let secret = "YOUR_APP_SECRET"
    let url = URL(string: "http://your-uap-domain/api/v1/simple/validate")!
    
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
    let timestamp = Int(Date().timeIntervalSince1970)
    var params: [String: Any] = [
        "app_id": appId,
        "ticket": ticket,
        "timestamp": timestamp
    ]
    
    params["sign"] = generateSign(secret: secret, params: params)
    
    request.httpBody = try? JSONSerialization.data(withJSONObject: params)
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let data = data {
            if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
                print(json)
            }
        }
    }
    task.resume()
}
            </pre>
          </div>
        </el-tab-pane>
      </el-tabs>
      
       <p><strong>响应成功 (示例):</strong></p>
       <div class="code-block">
        <pre>
{
  "valid": true,
  "user_id": 1001,
  "mobile": "13800138000",
  "mapped_key": "user_zhangsan",
  "mapped_email": "zhangsan@example.com"
}
        </pre>
      </div>
      <p>拿到用户信息后，您就可以在您的系统中创建会话 (Session/Token)，完成登录！</p>
    </div>
    
     <div class="section">
      <h3>3. 常见问题</h3>
       <ul>
          <li><strong>问：用户已经在其他应用登录了，还需要输密码吗？</strong><br>
          答：不需要。只要用户在 UAP 平台有有效会话，系统会自动完成认证并直接跳转回回调地址 (SSO)。</li>
          <li><strong>问：为什么跳转回来没有带 ticket？</strong><br>
          答：请检查应用配置的“回调地址”是否与实际跳转的地址完全一致 (包括 http/https)。</li>
       </ul>
     </div>
  </div>
</template>

<script setup lang="ts">
import { Download } from '@element-plus/icons-vue'
import { useHelpDocs } from '../../composables/useHelpDocs'

const { downloadDoc } = useHelpDocs()
const currentOrigin = window.location.origin
</script>

<style scoped>
@import './help.css';
</style>
