package handler

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"aijianzhan/platform/internal/audit"
	"aijianzhan/platform/internal/authx"
	"aijianzhan/platform/internal/dbsync"
	"aijianzhan/platform/internal/logic/applogic"
	"aijianzhan/platform/internal/svc"

	"github.com/zeromicro/go-zero/rest/httpx"
	"github.com/zeromicro/go-zero/rest/pathvar"
)

func requireDBSync(svcCtx *svc.ServiceContext, w http.ResponseWriter) bool {
	if svcCtx.DBSync == nil {
		authx.WriteError(w, http.StatusServiceUnavailable, "dbsync not enabled")
		return false
	}
	return true
}

func reconcileMinInterval(svcCtx *svc.ServiceContext) time.Duration {
	sec := 300
	if svcCtx != nil && svcCtx.Config.DBSync.ReconcileMinSec > 0 {
		sec = svcCtx.Config.DBSync.ReconcileMinSec
	}
	return time.Duration(sec) * time.Second
}

// sync 仅公司顶级权限（管理员 /「数据同步」）；智能体与编辑不可配。
func syncTenantID(r *http.Request) int64 {
	return authx.TenantID(r.Context())
}

func syncListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		tid := syncTenantID(r)
		if _, err := applogic.HealTenantSyncBind(r.Context(), svcCtx, tid); err != nil {
			// 列表仍继续，避免自愈失败挡运维
			_ = err
		}
		list, err := svcCtx.DBSync.Store().ListChannelsByTenant(tid)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, map[string]any{"items": list})
	}
}

func syncGetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		httpx.OkJson(w, ch)
	}
}

func syncSaveHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		var ch dbsync.Channel
		if err := json.NewDecoder(r.Body).Decode(&ch); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		if id := pathvar.Vars(r)["id"]; id != "" {
			ch.ID = id
			existing, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r))
			if err != nil {
				authx.WriteError(w, http.StatusNotFound, err.Error())
				return
			}
			_ = existing
		}
		// 强制归属当前公司，禁止客户端伪造 tenant_id
		ch.TenantID = syncTenantID(r)
		if err := dbsync.ValidateChannelConfig(&ch); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		// 对可达端做 UUID 主键 + 外键闭包校验（remote 通常为线上库）
		if err := dbsync.ValidateChannelAgainstDB(r.Context(), &ch); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		saved, err := svcCtx.DBSync.Store().SaveChannel(ch)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, saved)
	}
}

func syncDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		id := pathvar.Vars(r)["id"]
		tid := syncTenantID(r)
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(id, tid)
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		wasDefault := ch.IsSystemDefault
		svcCtx.DBSync.StopChannel(id)
		if err := svcCtx.DBSync.Store().DeleteChannel(id); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		// Z12h-1：默认同步可删；删后 Ensure+heal，账号落点自动挂回新默认通道
		out := map[string]any{"ok": true, "deleted_id": id, "was_system_default": wasDefault}
		if healed, hErr := applogic.HealTenantSyncBind(r.Context(), svcCtx, tid); hErr != nil {
			out["heal_error"] = hErr.Error()
		} else if healed != nil {
			out["channel_id"] = healed.ChannelID
			out["recreated_default"] = wasDefault || healed.ChannelCreated
			out["bindings_fixed"] = healed.BindingsFixed
			out["agents_fixed"] = healed.AgentsFixed
		}
		httpx.OkJson(w, out)
	}
}

func syncTestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		var body struct {
			Local  *dbsync.Endpoint `json:"local"`
			Remote *dbsync.Endpoint `json:"remote"`
			Side   string           `json:"side"` // local|remote|both
		}
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		out := map[string]any{}
		side := body.Side
		if side == "" {
			side = "both"
		}
		if (side == "local" || side == "both") && body.Local != nil {
			out["local"] = dbsync.TestEndpoint(r.Context(), *body.Local)
		}
		if (side == "remote" || side == "both") && body.Remote != nil {
			out["remote"] = dbsync.TestEndpoint(r.Context(), *body.Remote)
		}
		httpx.OkJson(w, out)
	}
}

func syncPrepareHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		if err := dbsync.PrepareChannel(r.Context(), ch); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, map[string]any{"ok": true, "message": "outbox + triggers ready"})
	}
}

func syncStartHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		id := pathvar.Vars(r)["id"]
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		if err := dbsync.PrepareChannel(r.Context(), ch); err != nil {
			authx.WriteError(w, http.StatusBadRequest, "prepare: "+err.Error())
			return
		}
		if err := svcCtx.DBSync.StartChannel(id); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, map[string]any{"ok": true, "running": true})
	}
}

func syncStopHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		id := pathvar.Vars(r)["id"]
		if _, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r)); err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		svcCtx.DBSync.StopChannel(id)
		httpx.OkJson(w, map[string]any{"ok": true, "running": false})
	}
}

func syncConflictsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// M3：LWW/冲突追溯仅平台超级管理员；公司 top 403
		authx.WriteError(w, http.StatusForbidden, "冲突/LWW 覆盖日志仅平台超级管理员可查")
	}
}

func syncResolveConflictHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		authx.WriteError(w, http.StatusForbidden, "冲突/LWW 覆盖日志仅平台超级管理员可操作")
	}
}

func syncReconcileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		if ok, wait := dbsync.CanReconcile(ch, reconcileMinInterval(svcCtx)); !ok {
			authx.WriteError(w, http.StatusTooManyRequests, dbsync.ReconcileTooSoonError(wait).Error())
			return
		}
		res, err := dbsync.ReconcileChannel(r.Context(), ch)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		now := time.Now().UTC()
		_ = svcCtx.DBSync.Store().PatchStats(ch.ID, func(c *dbsync.Channel) {
			c.LastReconcileAt = &now
		})
		dbsync.ScheduleCheckpoint(svcCtx.DBSync.Store(), ch, "reconcile")
		httpx.OkJson(w, res)
	}
}

func syncIngestHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		var body struct {
			Table  string           `json:"table"`
			Source string           `json:"source"` // 如 c / excel / api
			Rows   []map[string]any `json:"rows"`
		}
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		n, err := dbsync.IngestRows(r.Context(), ch, body.Table, body.Rows, body.Source)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, map[string]any{"ok": true, "ingested": n, "hint": "已写入本地并进入 outbox，将同步到线上"})
	}
}

// syncInspectHandler 控制台验同步：列出线上/本机库表 + 行数 + 列。
func syncInspectHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		side := strings.TrimSpace(r.URL.Query().Get("side"))
		if side == "" {
			side = "remote"
		}
		includeMeta := r.URL.Query().Get("include_sync_meta") == "1" || r.URL.Query().Get("include_sync_meta") == "true"
		var ep dbsync.Endpoint
		switch side {
		case "remote":
			ep = ch.Remote
		case "local":
			ep = ch.Local
		default:
			authx.WriteError(w, http.StatusBadRequest, "side must be remote|local")
			return
		}
		res, err := dbsync.InspectEndpoint(r.Context(), ep, side, includeMeta)
		if err != nil && (res == nil || !res.OK) {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, res)
	}
}

// syncPreviewHandler 预览单表内容（默认前 50 行）。
func syncPreviewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		var body struct {
			Side  string `json:"side"` // remote|local，默认 remote
			Table string `json:"table"`
			Limit int    `json:"limit"`
		}
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		side := strings.TrimSpace(body.Side)
		if side == "" {
			side = "remote"
		}
		var ep dbsync.Endpoint
		switch side {
		case "remote":
			ep = ch.Remote
		case "local":
			ep = ch.Local
		default:
			authx.WriteError(w, http.StatusBadRequest, "side must be remote|local")
			return
		}
		res, err := dbsync.PreviewTable(r.Context(), ep, body.Table, body.Limit)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, res)
	}
}

// syncDropTableHandler 控制台删业务表（仅当前 side 的 endpoint；不同步 DDL 到另一侧）。
func syncDropTableHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		var body struct {
			Side  string `json:"side"` // remote|local，默认 remote
			Table string `json:"table"`
		}
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		side := strings.TrimSpace(body.Side)
		if side == "" {
			side = "remote"
		}
		var ep dbsync.Endpoint
		switch side {
		case "remote":
			ep = ch.Remote
		case "local":
			ep = ch.Local
		default:
			authx.WriteError(w, http.StatusBadRequest, "side must be remote|local")
			return
		}
		res, err := dbsync.DropTableOnEndpoint(r.Context(), ep, side, body.Table)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		if svcCtx.Audit != nil {
			_ = svcCtx.Audit.Log(r.Context(), syncTenantID(r), authx.UserID(r.Context()), "dbsync.drop_table", audit.DetailJSON(map[string]any{
				"channel_id": ch.ID, "side": side, "table": body.Table, "dropped": res != nil && res.Dropped,
			}))
		}
		httpx.OkJson(w, res)
	}
}

func syncCheckpointMetaHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		id := pathvar.Vars(r)["id"]
		if _, err := svcCtx.DBSync.Store().GetChannelForTenant(id, syncTenantID(r)); err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		meta, err := dbsync.LoadCheckpointMeta(svcCtx.DBSync.Store(), id)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		httpx.OkJson(w, meta)
	}
}

func syncRestoreHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if !requireDBSync(svcCtx, w) {
			return
		}
		ch, err := svcCtx.DBSync.Store().GetChannelForTenant(pathvar.Vars(r)["id"], syncTenantID(r))
		if err != nil {
			authx.WriteError(w, http.StatusNotFound, err.Error())
			return
		}
		var body struct {
			Which   string `json:"which"`
			Confirm bool   `json:"confirm"`
		}
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		if !body.Confirm {
			authx.WriteError(w, http.StatusBadRequest, "请确认恢复：confirm=true")
			return
		}
		which := strings.TrimSpace(body.Which)
		if which == "" {
			which = "latest"
		}
		res, err := dbsync.RestoreCheckpoint(r.Context(), svcCtx.DBSync.Store(), ch, which)
		if err != nil {
			authx.WriteError(w, http.StatusBadRequest, err.Error())
			return
		}
		if svcCtx.Audit != nil {
			_ = svcCtx.Audit.Log(r.Context(), syncTenantID(r), authx.UserID(r.Context()), "dbsync.restore", audit.DetailJSON(map[string]any{
				"channel_id": ch.ID, "which": which, "synced_at": res.SyncedAt, "upserted": res.Upserted, "deleted": res.Deleted,
			}))
		}
		httpx.OkJson(w, res)
	}
}
