package channel

import (
	"bytes"
	"context"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"ops-assistant/config"
	"ops-assistant/models"

	"golang.org/x/crypto/pbkdf2"
	"gorm.io/gorm"
)

type UnifiedMessage struct {
	Platform string `json:"platform"`
	EventID  string `json:"event_id"`
	ChatID   string `json:"chat_id"`
	UserID   string `json:"user_id"`
	Text     string `json:"text"`
}

const (
	encPrefixV1 = "enc:v1:"
	encPrefixV2 = "enc:v2:"
)

var secretCipherV1 *cipherContext
var secretCipherV2 *cipherContext

type cipherContext struct {
	aead cipher.AEAD
}

func InitSecretCipher(key string) error {
	k1 := deriveKey32Legacy(key)
	block1, err := aes.NewCipher(k1)
	if err != nil {
		return err
	}
	aead1, err := cipher.NewGCM(block1)
	if err != nil {
		return err
	}
	secretCipherV1 = &cipherContext{aead: aead1}

	k2 := deriveKey32V2(key)
	block2, err := aes.NewCipher(k2)
	if err != nil {
		return err
	}
	aead2, err := cipher.NewGCM(block2)
	if err != nil {
		return err
	}
	secretCipherV2 = &cipherContext{aead: aead2}
	return nil
}

func deriveKey32Legacy(s string) []byte {
	b := []byte(s)
	out := make([]byte, 32)
	if len(b) >= 32 {
		copy(out, b[:32])
		return out
	}
	copy(out, b)
	for i := len(b); i < 32; i++ {
		out[i] = byte((i * 131) % 251)
	}
	return out
}

func deriveKey32V2(s string) []byte {
	if strings.TrimSpace(s) == "" {
		return make([]byte, 32)
	}
	// PBKDF2 for deterministic 32-byte key derivation
	return pbkdf2.Key([]byte(s), []byte("ops-assistant-v1"), 200000, 32, sha256.New)
}

func encryptString(plain string) (string, error) {
	if secretCipherV2 == nil {
		return plain, errors.New("cipher not initialized")
	}
	nonce := make([]byte, secretCipherV2.aead.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return "", err
	}
	ciphertext := secretCipherV2.aead.Seal(nil, nonce, []byte(plain), nil)
	buf := append(nonce, ciphertext...)
	return encPrefixV2 + base64.StdEncoding.EncodeToString(buf), nil
}

func decryptString(raw string) (string, error) {
	if !strings.HasPrefix(raw, encPrefixV1) && !strings.HasPrefix(raw, encPrefixV2) {
		return raw, nil
	}
	if strings.HasPrefix(raw, encPrefixV2) {
		if secretCipherV2 == nil {
			return "", errors.New("cipher not initialized")
		}
		data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, encPrefixV2))
		if err != nil {
			return "", err
		}
		ns := secretCipherV2.aead.NonceSize()
		if len(data) <= ns {
			return "", errors.New("invalid ciphertext")
		}
		nonce := data[:ns]
		ct := data[ns:]
		pt, err := secretCipherV2.aead.Open(nil, nonce, ct, nil)
		if err != nil {
			return "", err
		}
		return string(pt), nil
	}

	if secretCipherV1 == nil {
		return "", errors.New("cipher not initialized")
	}
	data, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(raw, encPrefixV1))
	if err != nil {
		return "", err
	}
	ns := secretCipherV1.aead.NonceSize()
	if len(data) <= ns {
		return "", errors.New("invalid ciphertext")
	}
	nonce := data[:ns]
	ct := data[ns:]
	pt, err := secretCipherV1.aead.Open(nil, nonce, ct, nil)
	if err != nil {
		return "", err
	}
	return string(pt), nil
}

func maybeDecrypt(raw string) string {
	if strings.TrimSpace(raw) == "" {
		return raw
	}
	pt, err := decryptString(raw)
	if err != nil {
		return raw
	}
	return pt
}

func MaybeDecryptPublic(raw string) string {
	return maybeDecrypt(raw)
}

func EncryptSecretJSON(raw string) string {
	if strings.TrimSpace(raw) == "" {
		return raw
	}
	if strings.HasPrefix(raw, encPrefixV1) || strings.HasPrefix(raw, encPrefixV2) {
		return raw
	}
	if secretCipherV2 == nil {
		return raw
	}
	enc, err := encryptString(raw)
	if err != nil {
		return raw
	}
	return enc
}

type telegramSecret struct {
	Token string `json:"token"`
}

type qqSecret struct {
	AppID  string `json:"appid"`
	Secret string `json:"secret"`
}

type feishuSecret struct {
	AppID             string `json:"app_id"`
	AppSecret         string `json:"app_secret"`
	VerificationToken string `json:"verification_token"`
	EncryptKey        string `json:"encrypt_key"`
}

func parseJSON(raw string, out any) {
	if strings.TrimSpace(raw) == "" {
		return
	}
	_ = json.Unmarshal([]byte(raw), out)
}

// ApplyChannelConfig 从数据库渠道配置覆盖运行时配置（优先级：DB > YAML）
func ApplyChannelConfig(db *gorm.DB, cfg *config.Config) error {
	var rows []models.ChannelConfig
	if err := db.Find(&rows).Error; err != nil {
		return err
	}

	for _, row := range rows {
		switch row.Platform {
		case "telegram":
			sec := telegramSecret{}
			parseJSON(maybeDecrypt(row.SecretJSON), &sec)
			cfg.Telegram.Enabled = row.Enabled
			if strings.TrimSpace(sec.Token) != "" {
				cfg.Telegram.Token = strings.TrimSpace(sec.Token)
			}
		case "qqbot_official":
			sec := qqSecret{}
			parseJSON(maybeDecrypt(row.SecretJSON), &sec)
			cfg.QQBot.Enabled = row.Enabled
			if strings.TrimSpace(sec.AppID) != "" {
				cfg.QQBot.AppID = strings.TrimSpace(sec.AppID)
			}
			if strings.TrimSpace(sec.Secret) != "" {
				cfg.QQBot.Secret = strings.TrimSpace(sec.Secret)
			}
		case "feishu":
			sec := feishuSecret{}
			parseJSON(maybeDecrypt(row.SecretJSON), &sec)
			cfg.Feishu.Enabled = row.Enabled
			if strings.TrimSpace(sec.AppID) != "" {
				cfg.Feishu.AppID = strings.TrimSpace(sec.AppID)
			}
			if strings.TrimSpace(sec.AppSecret) != "" {
				cfg.Feishu.AppSecret = strings.TrimSpace(sec.AppSecret)
			}
			if strings.TrimSpace(sec.VerificationToken) != "" {
				cfg.Feishu.VerificationToken = strings.TrimSpace(sec.VerificationToken)
			}
			if strings.TrimSpace(sec.EncryptKey) != "" {
				cfg.Feishu.EncryptKey = strings.TrimSpace(sec.EncryptKey)
			}
		}
	}
	return nil
}

func httpClient() *http.Client {
	return &http.Client{Timeout: 8 * time.Second}
}

func TestChannelConnectivity(ctx context.Context, row models.ChannelConfig) (status, detail string) {
	if !row.Enabled {
		return "disabled", "渠道未启用"
	}
	switch row.Platform {
	case "telegram":
		sec := telegramSecret{}
		parseJSON(maybeDecrypt(row.SecretJSON), &sec)
		if strings.TrimSpace(sec.Token) == "" {
			return "error", "telegram token 为空"
		}
		url := fmt.Sprintf("https://api.telegram.org/bot%s/getMe", strings.TrimSpace(sec.Token))
		req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		resp, err := httpClient().Do(req)
		if err != nil {
			return "error", err.Error()
		}
		defer resp.Body.Close()
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		if resp.StatusCode != 200 || !strings.Contains(string(body), `"ok":true`) {
			return "error", fmt.Sprintf("telegram getMe失败: http=%d", resp.StatusCode)
		}
		return "ok", "telegram getMe 成功"

	case "qqbot_official":
		sec := qqSecret{}
		parseJSON(maybeDecrypt(row.SecretJSON), &sec)
		if strings.TrimSpace(sec.AppID) == "" || strings.TrimSpace(sec.Secret) == "" {
			return "error", "qq appid/secret 为空"
		}
		payload, _ := json.Marshal(map[string]string{"appId": strings.TrimSpace(sec.AppID), "clientSecret": strings.TrimSpace(sec.Secret)})
		req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "https://bots.qq.com/app/getAppAccessToken", bytes.NewReader(payload))
		req.Header.Set("Content-Type", "application/json")
		resp, err := httpClient().Do(req)
		if err != nil {
			return "error", err.Error()
		}
		defer resp.Body.Close()
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		if resp.StatusCode != 200 || !strings.Contains(string(body), "access_token") {
			return "error", fmt.Sprintf("qq access token 获取失败: http=%d", resp.StatusCode)
		}
		return "ok", "qq access token 获取成功"

	case "feishu":
		sec := feishuSecret{}
		parseJSON(maybeDecrypt(row.SecretJSON), &sec)
		if strings.TrimSpace(sec.AppID) == "" || strings.TrimSpace(sec.AppSecret) == "" {
			return "error", "feishu app_id/app_secret 为空"
		}
		tk, err := GetFeishuTenantToken(ctx, strings.TrimSpace(sec.AppID), strings.TrimSpace(sec.AppSecret))
		if err != nil || strings.TrimSpace(tk) == "" {
			if err == nil {
				err = fmt.Errorf("token 为空")
			}
			return "error", err.Error()
		}
		return "ok", "feishu tenant_access_token 获取成功"
	default:
		return "error", "未知平台"
	}
}

func ParseFeishuInbound(body []byte, verificationToken string) (*UnifiedMessage, string, error) {
	// url_verification
	var verifyReq struct {
		Type      string `json:"type"`
		Challenge string `json:"challenge"`
		Token     string `json:"token"`
	}
	if err := json.Unmarshal(body, &verifyReq); err == nil && verifyReq.Type == "url_verification" {
		if strings.TrimSpace(verificationToken) != "" && verifyReq.Token != verificationToken {
			return nil, "", fmt.Errorf("verification token mismatch")
		}
		return nil, verifyReq.Challenge, nil
	}

	var event struct {
		Header struct {
			EventID   string `json:"event_id"`
			EventType string `json:"event_type"`
		} `json:"header"`
		Event struct {
			Sender struct {
				SenderID struct {
					OpenID string `json:"open_id"`
				} `json:"sender_id"`
			} `json:"sender"`
			Message struct {
				MessageID string `json:"message_id"`
				ChatID    string `json:"chat_id"`
				Content   string `json:"content"`
			} `json:"message"`
		} `json:"event"`
	}
	if err := json.Unmarshal(body, &event); err != nil {
		return nil, "", err
	}

	if event.Header.EventType != "im.message.receive_v1" {
		return nil, "", nil
	}

	eventID := strings.TrimSpace(event.Header.EventID)
	if eventID == "" {
		eventID = strings.TrimSpace(event.Event.Message.MessageID)
	}
	if eventID == "" {
		return nil, "", fmt.Errorf("missing event id")
	}

	var content struct {
		Text string `json:"text"`
	}
	_ = json.Unmarshal([]byte(event.Event.Message.Content), &content)
	text := strings.TrimSpace(content.Text)
	if text == "" {
		return nil, "", nil
	}

	return &UnifiedMessage{
		Platform: "feishu",
		EventID:  eventID,
		ChatID:   strings.TrimSpace(event.Event.Message.ChatID),
		UserID:   strings.TrimSpace(event.Event.Sender.SenderID.OpenID),
		Text:     text,
	}, "", nil
}

func GetFeishuTenantToken(ctx context.Context, appID, appSecret string) (string, error) {
	payload, _ := json.Marshal(map[string]string{"app_id": appID, "app_secret": appSecret})
	req, _ := http.NewRequestWithContext(ctx, http.MethodPost, "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", bytes.NewReader(payload))
	req.Header.Set("Content-Type", "application/json")
	resp, err := httpClient().Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(io.LimitReader(resp.Body, 8192))
	if resp.StatusCode != 200 {
		return "", fmt.Errorf("http=%d", resp.StatusCode)
	}

	var out struct {
		Code              int    `json:"code"`
		Msg               string `json:"msg"`
		TenantAccessToken string `json:"tenant_access_token"`
	}
	if err := json.Unmarshal(body, &out); err != nil {
		return "", err
	}
	if out.Code != 0 || strings.TrimSpace(out.TenantAccessToken) == "" {
		if out.Msg == "" {
			out.Msg = "获取token失败"
		}
		return "", fmt.Errorf(out.Msg)
	}
	return out.TenantAccessToken, nil
}

func SendFeishuText(ctx context.Context, tenantToken, receiveID, text string) error {
	contentBytes, _ := json.Marshal(map[string]string{"text": text})
	payload, _ := json.Marshal(map[string]string{
		"receive_id": receiveID,
		"msg_type":   "text",
		"content":    string(contentBytes),
	})
	url := "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id"
	req, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+tenantToken)
	resp, err := httpClient().Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 8192))
	if resp.StatusCode != 200 {
		return fmt.Errorf("http=%d", resp.StatusCode)
	}
	if !strings.Contains(string(body), `"code":0`) {
		return fmt.Errorf("feishu send failed")
	}
	return nil
}
