package applogic

import (
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"time"

	"aijianzhan/platform/internal/agentstore"
	"aijianzhan/platform/internal/authx"
	"aijianzhan/platform/internal/bindcodestore"
	"aijianzhan/platform/internal/dbsync"
	"aijianzhan/platform/internal/schema"
	"aijianzhan/platform/internal/smsstore"
	"aijianzhan/platform/internal/userstore"
)

// AgentMe Z12b/Z12h：智能体自查绑定；通道缺失时自愈。
func (l *AuthLogic) AgentMe() (*agentstore.Account, error) {
	if !authx.IsAgent(authx.Role(l.ctx)) {
		return nil, fmt.Errorf("仅智能体可访问")
	}
	if l.svcCtx.Agents == nil {
		return nil, fmt.Errorf("agent store unavailable")
	}
	tid := authx.TenantID(l.ctx)
	aid := authx.UserID(l.ctx)
	acc, err := l.svcCtx.Agents.Get(l.ctx, tid, aid)
	if err != nil {
		return nil, err
	}
	if l.svcCtx.DBSync != nil {
		if _, hErr := HealTenantSyncBind(l.ctx, l.svcCtx, tid); hErr != nil {
			log.Printf("agent me heal: %v", hErr)
		} else if refreshed, gErr := l.svcCtx.Agents.Get(l.ctx, tid, aid); gErr == nil && refreshed != nil {
			acc = refreshed
		}
	}
	return acc, nil
}

type BindCodeCreateReq struct {
	ChannelID    string `json:"channel_id"`
	OnlineDBID   string `json:"online_db_id"`
	DatabaseName string `json:"database_name"`
	MaxUses      int    `json:"max_uses"`
	ExpiresHours int    `json:"expires_hours"` // 0=168h
	Note         string `json:"note"`
}

func (l *AuthLogic) CreateBindCode(req BindCodeCreateReq) (*bindcodestore.BindCode, error) {
	if l.svcCtx.BindCodes == nil {
		return nil, fmt.Errorf("bind code store unavailable")
	}
	tid := authx.TenantID(l.ctx)
	if tid <= 0 {
		return nil, fmt.Errorf("未加入公司")
	}
	channelID := strings.TrimSpace(req.ChannelID)
	online := strings.TrimSpace(req.OnlineDBID)
	rawDB := strings.TrimSpace(req.DatabaseName)
	dbName := ""
	displayNote := ""
	if rawDB != "" {
		if schema.ValidDBName(rawDB) {
			dbName = rawDB
		} else {
			// 接受中文等展示名：落码存物理哈希；原文记入 note 便于对照
			dbName = schema.HashLabelDBName(rawDB, 0)
			displayNote = rawDB
		}
	}
	if channelID == "" && l.svcCtx.DBSync != nil {
		// Z12h：生成绑定码前确保默认通道存在（含误删自愈）
		if healed, err := HealTenantSyncBind(l.ctx, l.svcCtx, tid); err != nil {
			return nil, fmt.Errorf("无默认同步通道：%w", err)
		} else if healed != nil && strings.TrimSpace(healed.ChannelID) != "" {
			channelID = healed.ChannelID
			if online == "" {
				online = dbsync.ResolveOnlineDBID("", channelID)
			}
		}
	}
	if channelID == "" {
		return nil, fmt.Errorf("请先启用智能体以生成默认同步通道，或指定 channel_id")
	}
	if online == "" {
		online = dbsync.ResolveOnlineDBID("", channelID)
	}
	note := strings.TrimSpace(req.Note)
	if displayNote != "" {
		if note != "" {
			note = note + " | "
		}
		note += "display:" + displayNote
	}
	exp := time.Duration(req.ExpiresHours) * time.Hour
	return l.svcCtx.BindCodes.Create(l.ctx, tid, authx.UserID(l.ctx), bindcodestore.CreateInput{
		ChannelID:    channelID,
		OnlineDBID:   online,
		DatabaseName: dbName,
		MaxUses:      req.MaxUses,
		ExpiresIn:    exp,
		Note:         note,
	})
}

func (l *AuthLogic) ListBindCodes() ([]bindcodestore.BindCode, error) {
	if l.svcCtx.BindCodes == nil {
		return nil, fmt.Errorf("bind code store unavailable")
	}
	tid := authx.TenantID(l.ctx)
	return l.svcCtx.BindCodes.List(l.ctx, tid)
}

func (l *AuthLogic) RevokeBindCode(code string) error {
	if l.svcCtx.BindCodes == nil {
		return fmt.Errorf("bind code store unavailable")
	}
	return l.svcCtx.BindCodes.Revoke(l.ctx, authx.TenantID(l.ctx), code)
}

type BindCodeRedeemReq struct {
	Code    string `json:"code"`
	HostKey string `json:"host_key"`
	Name    string `json:"name"` // 可选：无 agent 时注册用
}

type BindCodeRedeemResp struct {
	OK           bool     `json:"ok"`
	TenantID     int64    `json:"tenant_id"`
	AgentID      int64    `json:"agent_id"`
	ClientID     string   `json:"client_id,omitempty"`
	ClientSecret string   `json:"client_secret,omitempty"` // Z28：绑定成功回传，便于直接换票
	ChannelID    string   `json:"channel_id"`
	OnlineDBID   string   `json:"online_db_id"`
	DatabaseName string   `json:"database_name,omitempty"`
	SyncBound    bool     `json:"sync_bound"`
	Permissions  []string `json:"permissions"`
	Message      string   `json:"message,omitempty"`
}

func (l *AuthLogic) RedeemBindCode(req BindCodeRedeemReq) (*BindCodeRedeemResp, error) {
	if l.svcCtx.BindCodes == nil || l.svcCtx.Agents == nil {
		return nil, fmt.Errorf("bind service unavailable")
	}
	code := strings.TrimSpace(req.Code)
	hostKey := strings.TrimSpace(req.HostKey)
	if code == "" || hostKey == "" {
		return nil, fmt.Errorf("code and host_key required")
	}
	bc, err := l.svcCtx.BindCodes.Redeem(l.ctx, code)
	if err != nil {
		return nil, err
	}
	acc, err := l.svcCtx.Agents.FindByHostKey(l.ctx, hostKey)
	if err != nil {
		name := strings.TrimSpace(req.Name)
		if name == "" {
			name = "离线终端"
		}
		created, secret, _, regErr := l.svcCtx.Agents.Register(l.ctx, bc.TenantID, name, hostKey)
		if regErr != nil {
			return nil, fmt.Errorf("register agent: %w", regErr)
		}
		_ = secret
		acc = created
	}
	online := strings.TrimSpace(bc.OnlineDBID)
	if online == "" {
		online = dbsync.ResolveOnlineDBID("", bc.ChannelID)
	}
	dbName := schema.SafeSyncDatabaseName(bc.DatabaseName, acc.AgentID, 0)
	updated, err := l.svcCtx.Agents.AttachSyncBind(l.ctx, acc.AgentID, bc.TenantID, bc.ChannelID, online, dbName, true)
	if err != nil {
		return nil, err
	}
	updated, err = l.ensureYuhengAgentPerms(updated)
	if err != nil {
		return nil, err
	}
	// Z28：回传可换票凭证
	secret, err := l.svcCtx.Agents.RotateSecret(l.ctx, updated.TenantID, updated.AgentID)
	if err != nil {
		return nil, fmt.Errorf("rotate client_secret: %w", err)
	}
	_ = l.writeBindAudit("bind_code_redeem", updated.TenantID, updated.AgentID, map[string]any{
		"code": bc.Code, "channel_id": bc.ChannelID, "online_db_id": online,
	})
	return &BindCodeRedeemResp{
		OK:           true,
		TenantID:     updated.TenantID,
		AgentID:      updated.AgentID,
		ClientID:     updated.ClientID,
		ClientSecret: secret,
		ChannelID:    updated.ChannelID,
		OnlineDBID:   updated.OnlineDBID,
		DatabaseName: updated.DatabaseName,
		SyncBound:    true,
		Permissions:  append([]string{}, updated.Perms...),
		Message:      "绑定并授权完成；请保存 client_id/client_secret 用于换票",
	}, nil
}

type PhoneLookupReq struct {
	Phone string `json:"phone"`
}

type PhoneLookupResp struct {
	Exists      bool   `json:"exists"`
	TenantID    int64  `json:"tenant_id,omitempty"`
	TenantName  string `json:"tenant_name,omitempty"`
	MaskedName  string `json:"masked_name,omitempty"`
	NeedConfirm bool   `json:"need_confirm"`
	Message     string `json:"message,omitempty"`
	// Z13c-2：试运行 RequireForBind=false 时为 false；正式开启后为 true（宇恒同号可 attested 免验）
	SMSRequiredUnlessAttested bool `json:"sms_required_unless_attested"`
}

func (l *AuthLogic) bindSMSRequired() bool {
	if l == nil || l.svcCtx == nil {
		return false
	}
	if strings.EqualFold(strings.TrimSpace(l.svcCtx.Config.SMS.Provider), "off") {
		return false
	}
	return l.svcCtx.Config.SMS.RequireForBind
}

// BindPolicy 公开策略，供宇恒决定是否弹短信 / 走凭票。
type BindPolicyResp struct {
	RequireForBind      bool   `json:"require_for_bind"`
	SMSProvider         string `json:"sms_provider"`
	YuhengTicketEnabled bool   `json:"yuheng_ticket_enabled"`
	TrialMode           bool   `json:"trial_mode"` // !require_for_bind
	Message             string `json:"message,omitempty"`
}

func (l *AuthLogic) BindPolicy() BindPolicyResp {
	req := l.bindSMSRequired()
	ticketOn := l.svcCtx != nil && l.svcCtx.Config.Agent.YuhengTicket.Enabled
	prov := ""
	if l.svcCtx != nil {
		prov = strings.TrimSpace(l.svcCtx.Config.SMS.Provider)
	}
	msg := "试运行：绑定可不校验短信，仍须用户确认"
	if req {
		msg = "正式：异号须 sms_code；同号请用宇恒凭票 ticket-exchange（或 attested，若未启凭票）"
		if ticketOn {
			msg = "正式：异号须 sms_code；同号请用 POST /api/v1/auth/yuheng/ticket-exchange（已禁 attested_same_phone）"
		}
	}
	return BindPolicyResp{
		RequireForBind:      req,
		SMSProvider:         prov,
		YuhengTicketEnabled: ticketOn,
		TrialMode:           !req,
		Message:             msg,
	}
}

func maskDisplayName(name string) string {
	name = strings.TrimSpace(name)
	if name == "" {
		return "***"
	}
	r := []rune(name)
	if len(r) == 1 {
		return string(r[0]) + "*"
	}
	if len(r) == 2 {
		return string(r[0]) + "*"
	}
	return string(r[0]) + strings.Repeat("*", len(r)-2) + string(r[len(r)-1])
}

func (l *AuthLogic) PhoneLookup(req PhoneLookupReq) (*PhoneLookupResp, error) {
	smsReq := l.bindSMSRequired()
	if l.svcCtx.Users == nil {
		return nil, fmt.Errorf("user store unavailable")
	}
	phone, err := userstore.NormalizePhone(req.Phone)
	if err != nil {
		return &PhoneLookupResp{Exists: false, Message: "手机号格式不正确", SMSRequiredUnlessAttested: smsReq}, nil
	}
	u, err := l.svcCtx.Users.GetByPhone(l.ctx, phone)
	if err != nil || u == nil {
		return &PhoneLookupResp{Exists: false, NeedConfirm: false, Message: "无此成员；请使用绑定码或联系管理员", SMSRequiredUnlessAttested: smsReq}, nil
	}
	if u.TenantID <= 0 {
		return &PhoneLookupResp{Exists: true, NeedConfirm: false, Message: "该手机号账号尚未加入公司", SMSRequiredUnlessAttested: smsReq}, nil
	}
	tenantName := ""
	if t, err := l.svcCtx.Users.GetTenant(l.ctx, u.TenantID); err == nil && t != nil {
		tenantName = t.Name
	}
	msg := fmt.Sprintf("已找到账号「%s」所属「%s」，是否绑定到本机？", maskDisplayName(u.DisplayName), tenantName)
	if !smsReq {
		msg += "（试运行：短信验证已关闭，确认即可）"
	} else if l.svcCtx.Config.Agent.YuhengTicket.Enabled {
		msg += "（正式：同号请用宇恒凭票 ticket-exchange；异号须 sms_code）"
	} else {
		msg += "（正式：宇恒同号可 attested_same_phone=true；异号须 sms_code）"
	}
	return &PhoneLookupResp{
		Exists:                    true,
		TenantID:                  u.TenantID,
		TenantName:                tenantName,
		MaskedName:                maskDisplayName(u.DisplayName),
		NeedConfirm:               true,
		SMSRequiredUnlessAttested: smsReq,
		Message:                   msg,
	}, nil
}

type PhoneConfirmReq struct {
	Phone     string `json:"phone"`
	HostKey   string `json:"host_key"`
	Name      string `json:"name"`
	Confirm   bool   `json:"confirm"` // 必须 true
	LocalDBID string `json:"local_database_id"`
	// Z13c-2：输入号=宇恒已绑手机时，宇恒置 true 可免短信；异号必须带 sms_code
	AttestedSamePhone bool   `json:"attested_same_phone"`
	SMSCode           string `json:"sms_code"`
}

type PhoneConfirmResp struct {
	OK           bool     `json:"ok"`
	TenantID     int64    `json:"tenant_id"`
	AgentID      int64    `json:"agent_id"`
	ClientID     string   `json:"client_id,omitempty"`
	ClientSecret string   `json:"client_secret,omitempty"` // Z28：绑定成功回传
	ChannelID    string   `json:"channel_id"`
	OnlineDBID   string   `json:"online_db_id"`
	DatabaseName string   `json:"database_name,omitempty"`
	SyncBound    bool     `json:"sync_bound"`
	Permissions  []string `json:"permissions"`
	Message      string   `json:"message,omitempty"`
}

func (l *AuthLogic) requirePhoneBindProof(phone string, attested bool, smsCode string) error {
	// 正式模式且启用宇恒凭票：禁止明文 attested（须走 ticket-exchange）
	formal := l.bindSMSRequired()
	if attested && formal && l.svcCtx.Config.Agent.YuhengTicket.Enabled {
		return fmt.Errorf("已启用宇恒凭票：请使用 POST /api/v1/auth/yuheng/ticket-exchange，勿再传 attested_same_phone")
	}
	if attested {
		return nil
	}
	// 试运行：RequireForBind=false 或 Provider=off，跳过短信
	if !formal {
		return nil
	}
	code := strings.TrimSpace(smsCode)
	if code == "" {
		return fmt.Errorf("须提供 sms_code，或使用宇恒凭票 ticket-exchange（同号）")
	}
	if l.svcCtx.SMS == nil {
		return fmt.Errorf("短信服务未启用，无法校验验证码")
	}
	if err := l.svcCtx.SMS.Consume(smsstore.PurposeBind, phone, code); err == nil {
		return nil
	}
	if err := l.svcCtx.SMS.Consume(smsstore.PurposeLogin, phone, code); err == nil {
		return nil
	}
	return fmt.Errorf("短信验证码无效或已过期")
}

func (l *AuthLogic) PhoneConfirm(req PhoneConfirmReq) (*PhoneConfirmResp, error) {
	if !req.Confirm {
		return nil, fmt.Errorf("须明确确认绑定（confirm=true）")
	}
	if l.svcCtx.Users == nil || l.svcCtx.Agents == nil || l.svcCtx.DBSync == nil {
		return nil, fmt.Errorf("bind service unavailable")
	}
	phone, err := userstore.NormalizePhone(req.Phone)
	if err != nil {
		return nil, fmt.Errorf("手机号格式不正确")
	}
	if err := l.requirePhoneBindProof(phone, req.AttestedSamePhone, req.SMSCode); err != nil {
		return nil, err
	}
	hostKey := strings.TrimSpace(req.HostKey)
	if hostKey == "" {
		return nil, fmt.Errorf("host_key required")
	}
	u, err := l.svcCtx.Users.GetByPhone(l.ctx, phone)
	if err != nil || u == nil {
		return nil, fmt.Errorf("无此成员；请使用绑定码")
	}
	if u.TenantID <= 0 {
		return nil, fmt.Errorf("该账号尚未加入公司")
	}
	updated, err := l.bindUserHostSync(u, hostKey, req.Name, req.LocalDBID, "phone-confirm")
	if err != nil {
		return nil, err
	}
	updated, err = l.ensureYuhengAgentPerms(updated)
	if err != nil {
		return nil, err
	}
	secret, err := l.svcCtx.Agents.RotateSecret(l.ctx, updated.TenantID, updated.AgentID)
	if err != nil {
		return nil, fmt.Errorf("rotate client_secret: %w", err)
	}
	_ = l.writeBindAudit("phone_confirm_bind", u.TenantID, updated.AgentID, map[string]any{
		"phone": phone, "user_id": u.UserID, "channel_id": updated.ChannelID, "online_db_id": updated.OnlineDBID,
	})
	return &PhoneConfirmResp{
		OK:           true,
		TenantID:     updated.TenantID,
		AgentID:      updated.AgentID,
		ClientID:     updated.ClientID,
		ClientSecret: secret,
		ChannelID:    updated.ChannelID,
		OnlineDBID:   updated.OnlineDBID,
		DatabaseName: updated.DatabaseName,
		SyncBound:    true,
		Permissions:  append([]string{}, updated.Perms...),
		Message:      "绑定并授权完成；请保存 client_id/client_secret 用于换票",
	}, nil
}

func (l *AuthLogic) writeBindAudit(action string, tenantID, agentID int64, detail map[string]any) error {
	if l.svcCtx.Audit == nil {
		return nil
	}
	if detail == nil {
		detail = map[string]any{}
	}
	detail["agent_id"] = agentID
	b, _ := json.Marshal(detail)
	return l.svcCtx.Audit.Log(l.ctx, tenantID, authx.UserID(l.ctx), action, string(b))
}
