package http

import (
	"encoding/json"
	"log/slog"
	"net/http"
	"strconv"
	"strings"

	"github.com/google/uuid"

	"github.com/nextlevelbuilder/goclaw/internal/bus"
	"github.com/nextlevelbuilder/goclaw/internal/i18n"
	"github.com/nextlevelbuilder/goclaw/internal/store"
	"github.com/nextlevelbuilder/goclaw/pkg/protocol"
)

// ChannelInstancesHandler handles channel instance CRUD endpoints.
type ChannelInstancesHandler struct {
	store        store.ChannelInstanceStore
	agentStore   store.AgentStore
	contactStore store.ContactStore
	token        string
	msgBus       *bus.MessageBus
}

// NewChannelInstancesHandler creates a handler for channel instance management endpoints.
func NewChannelInstancesHandler(s store.ChannelInstanceStore, agentStore store.AgentStore, contactStore store.ContactStore, token string, msgBus *bus.MessageBus) *ChannelInstancesHandler {
	return &ChannelInstancesHandler{store: s, agentStore: agentStore, contactStore: contactStore, token: token, msgBus: msgBus}
}

// RegisterRoutes registers all channel instance routes on the given mux.
func (h *ChannelInstancesHandler) RegisterRoutes(mux *http.ServeMux) {
	mux.HandleFunc("GET /v1/channels/instances", h.auth(h.handleList))
	mux.HandleFunc("POST /v1/channels/instances", h.auth(h.handleCreate))
	mux.HandleFunc("GET /v1/channels/instances/{id}", h.auth(h.handleGet))
	mux.HandleFunc("PUT /v1/channels/instances/{id}", h.auth(h.handleUpdate))
	mux.HandleFunc("DELETE /v1/channels/instances/{id}", h.auth(h.handleDelete))

	// Channel contacts (global, not per-agent)
	if h.contactStore != nil {
		mux.HandleFunc("GET /v1/contacts", h.auth(h.handleListContacts))
		mux.HandleFunc("GET /v1/contacts/resolve", h.auth(h.handleResolveContacts))
	}

	// Group file writers (nested under channel instances)
	if h.agentStore != nil {
		mux.HandleFunc("GET /v1/channels/instances/{id}/writers/groups", h.auth(h.handleWriterGroups))
		mux.HandleFunc("GET /v1/channels/instances/{id}/writers", h.auth(h.handleListWriters))
		mux.HandleFunc("POST /v1/channels/instances/{id}/writers", h.auth(h.handleAddWriter))
		mux.HandleFunc("DELETE /v1/channels/instances/{id}/writers/{userId}", h.auth(h.handleRemoveWriter))
	}
}

func (h *ChannelInstancesHandler) auth(next http.HandlerFunc) http.HandlerFunc {
	return requireAuth(h.token, "", next)
}

func (h *ChannelInstancesHandler) emitCacheInvalidate() {
	if h.msgBus == nil {
		return
	}
	h.msgBus.Broadcast(bus.Event{
		Name:    protocol.EventCacheInvalidate,
		Payload: bus.CacheInvalidatePayload{Kind: bus.CacheKindChannelInstances},
	})
}

func (h *ChannelInstancesHandler) handleList(w http.ResponseWriter, r *http.Request) {
	opts := store.ChannelInstanceListOpts{
		Limit:  50,
		Offset: 0,
	}

	if v := r.URL.Query().Get("search"); v != "" {
		opts.Search = v
	}
	if v := r.URL.Query().Get("limit"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 {
			opts.Limit = n
		}
	}
	if v := r.URL.Query().Get("offset"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n >= 0 {
			opts.Offset = n
		}
	}

	instances, err := h.store.ListPaged(r.Context(), opts)
	if err != nil {
		slog.Error("channel_instances.list", "error", err)
		locale := store.LocaleFromContext(r.Context())
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToList, "instances")})
		return
	}

	total, _ := h.store.CountInstances(r.Context(), opts)

	result := make([]map[string]any, 0, len(instances))
	for _, inst := range instances {
		result = append(result, maskInstanceHTTP(inst))
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"instances": result,
		"total":     total,
		"limit":     opts.Limit,
		"offset":    opts.Offset,
	})
}

func (h *ChannelInstancesHandler) handleCreate(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	var body struct {
		Name        string          `json:"name"`
		DisplayName string          `json:"display_name"`
		ChannelType string          `json:"channel_type"`
		AgentID     string          `json:"agent_id"`
		Credentials json.RawMessage `json:"credentials"`
		Config      json.RawMessage `json:"config"`
		Enabled     *bool           `json:"enabled"`
	}
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&body); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
		return
	}

	if body.Name == "" || body.ChannelType == "" || body.AgentID == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "name, channel_type, and agent_id")})
		return
	}

	if !isValidChannelType(body.ChannelType) {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidChannelType)})
		return
	}

	agentID, err := uuid.Parse(body.AgentID)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "agent")})
		return
	}

	enabled := true
	if body.Enabled != nil {
		enabled = *body.Enabled
	}

	userID := store.UserIDFromContext(r.Context())

	inst := &store.ChannelInstanceData{
		Name:        body.Name,
		DisplayName: body.DisplayName,
		ChannelType: body.ChannelType,
		AgentID:     agentID,
		Credentials: body.Credentials,
		Config:      body.Config,
		Enabled:     enabled,
		CreatedBy:   userID,
	}

	if err := h.store.Create(r.Context(), inst); err != nil {
		slog.Error("channel_instances.create", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}

	h.emitCacheInvalidate()
	emitAudit(h.msgBus, r, "channel_instance.created", "channel_instance", inst.ID.String())
	writeJSON(w, http.StatusCreated, maskInstanceHTTP(*inst))
}

func (h *ChannelInstancesHandler) handleGet(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "instance")})
		return
	}

	inst, err := h.store.Get(r.Context(), id)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgInstanceNotFound)})
		return
	}

	writeJSON(w, http.StatusOK, maskInstanceHTTP(*inst))
}

func (h *ChannelInstancesHandler) handleUpdate(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "instance")})
		return
	}

	var updates map[string]any
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&updates); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
		return
	}

	// Allowlist: only permit known channel instance columns.
	updates = filterAllowedKeys(updates, channelInstanceAllowedFields)

	if err := h.store.Update(r.Context(), id, updates); err != nil {
		slog.Error("channel_instances.update", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}

	h.emitCacheInvalidate()
	emitAudit(h.msgBus, r, "channel_instance.updated", "channel_instance", id.String())
	writeJSON(w, http.StatusOK, map[string]string{"status": "updated"})
}

func (h *ChannelInstancesHandler) handleDelete(w http.ResponseWriter, r *http.Request) {
	locale := store.LocaleFromContext(r.Context())
	id, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "instance")})
		return
	}

	// Look up instance to check if it's a default (seeded) instance.
	inst, err := h.store.Get(r.Context(), id)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgInstanceNotFound)})
		return
	}
	if store.IsDefaultChannelInstance(inst.Name) {
		writeJSON(w, http.StatusForbidden, map[string]string{"error": i18n.T(locale, i18n.MsgCannotDeleteDefaultInst)})
		return
	}

	if err := h.store.Delete(r.Context(), id); err != nil {
		slog.Error("channel_instances.delete", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
		return
	}

	h.emitCacheInvalidate()
	emitAudit(h.msgBus, r, "channel_instance.deleted", "channel_instance", id.String())
	writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
}

// maskInstanceHTTP returns a map with credentials masked for HTTP responses.
func maskInstanceHTTP(inst store.ChannelInstanceData) map[string]any {
	result := map[string]any{
		"id":              inst.ID,
		"name":            inst.Name,
		"display_name":    inst.DisplayName,
		"channel_type":    inst.ChannelType,
		"agent_id":        inst.AgentID,
		"config":          inst.Config,
		"enabled":         inst.Enabled,
		"is_default":      store.IsDefaultChannelInstance(inst.Name),
		"has_credentials": len(inst.Credentials) > 0,
		"created_by":      inst.CreatedBy,
		"created_at":      inst.CreatedAt,
		"updated_at":      inst.UpdatedAt,
	}

	if len(inst.Credentials) > 0 {
		var raw map[string]any
		if json.Unmarshal(inst.Credentials, &raw) == nil {
			masked := make(map[string]any, len(raw))
			for k := range raw {
				masked[k] = "***"
			}
			result["credentials"] = masked
		} else {
			result["credentials"] = map[string]string{}
		}
	} else {
		result["credentials"] = map[string]string{}
	}

	return result
}

// --- Group file writers ---

// resolveAgentID looks up the channel instance and returns its agent_id.
func (h *ChannelInstancesHandler) resolveAgentID(w http.ResponseWriter, r *http.Request) (uuid.UUID, bool) {
	locale := store.LocaleFromContext(r.Context())
	instID, err := uuid.Parse(r.PathValue("id"))
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidID, "instance")})
		return uuid.Nil, false
	}
	inst, err := h.store.Get(r.Context(), instID)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]string{"error": i18n.T(locale, i18n.MsgInstanceNotFound)})
		return uuid.Nil, false
	}
	return inst.AgentID, true
}

func (h *ChannelInstancesHandler) handleWriterGroups(w http.ResponseWriter, r *http.Request) {
	agentID, ok := h.resolveAgentID(w, r)
	if !ok {
		return
	}
	groups, err := h.agentStore.ListGroupFileWriterGroups(r.Context(), agentID)
	if err != nil {
		slog.Error("channel_instances.writer_groups", "error", err)
		locale := store.LocaleFromContext(r.Context())
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToList, "writer groups")})
		return
	}
	if groups == nil {
		groups = []store.GroupWriterGroupInfo{}
	}
	writeJSON(w, http.StatusOK, map[string]any{"groups": groups})
}

func (h *ChannelInstancesHandler) handleListWriters(w http.ResponseWriter, r *http.Request) {
	agentID, ok := h.resolveAgentID(w, r)
	if !ok {
		return
	}
	locale := store.LocaleFromContext(r.Context())
	groupID := r.URL.Query().Get("group_id")
	if groupID == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "group_id")})
		return
	}
	writers, err := h.agentStore.ListGroupFileWriters(r.Context(), agentID, groupID)
	if err != nil {
		slog.Error("channel_instances.list_writers", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToList, "writers")})
		return
	}
	if writers == nil {
		writers = []store.GroupFileWriterData{}
	}
	writeJSON(w, http.StatusOK, map[string]any{"writers": writers})
}

func (h *ChannelInstancesHandler) handleAddWriter(w http.ResponseWriter, r *http.Request) {
	agentID, ok := h.resolveAgentID(w, r)
	if !ok {
		return
	}
	locale := store.LocaleFromContext(r.Context())
	var body struct {
		GroupID     string `json:"group_id"`
		UserID      string `json:"user_id"`
		DisplayName string `json:"display_name"`
		Username    string `json:"username"`
	}
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<16)).Decode(&body); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
		return
	}
	if body.GroupID == "" || body.UserID == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "group_id and user_id")})
		return
	}
	if err := h.agentStore.AddGroupFileWriter(r.Context(), agentID, body.GroupID, body.UserID, body.DisplayName, body.Username); err != nil {
		slog.Error("channel_instances.add_writer", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToCreate, "writer", err.Error())})
		return
	}
	writeJSON(w, http.StatusCreated, map[string]string{"status": "added"})
}

func (h *ChannelInstancesHandler) handleRemoveWriter(w http.ResponseWriter, r *http.Request) {
	agentID, ok := h.resolveAgentID(w, r)
	if !ok {
		return
	}
	locale := store.LocaleFromContext(r.Context())
	userID := r.PathValue("userId")
	groupID := r.URL.Query().Get("group_id")
	if groupID == "" || userID == "" {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "group_id and userId")})
		return
	}
	if err := h.agentStore.RemoveGroupFileWriter(r.Context(), agentID, groupID, userID); err != nil {
		slog.Error("channel_instances.remove_writer", "error", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToDelete, "writer", err.Error())})
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"status": "removed"})
}

// --- Channel contacts ---

func (h *ChannelInstancesHandler) handleListContacts(w http.ResponseWriter, r *http.Request) {
	opts := store.ContactListOpts{
		Limit:  50,
		Offset: 0,
	}

	if v := r.URL.Query().Get("search"); v != "" {
		opts.Search = v
	}
	if v := r.URL.Query().Get("channel_type"); v != "" {
		opts.ChannelType = v
	}
	if v := r.URL.Query().Get("peer_kind"); v != "" {
		opts.PeerKind = v
	}
	if v := r.URL.Query().Get("limit"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 {
			opts.Limit = n
		}
	}
	if v := r.URL.Query().Get("offset"); v != "" {
		if n, err := strconv.Atoi(v); err == nil && n >= 0 {
			opts.Offset = n
		}
	}

	contacts, err := h.contactStore.ListContacts(r.Context(), opts)
	if err != nil {
		slog.Error("contacts.list", "error", err)
		locale := store.LocaleFromContext(r.Context())
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToList, "contacts")})
		return
	}
	if contacts == nil {
		contacts = []store.ChannelContact{}
	}

	total, countErr := h.contactStore.CountContacts(r.Context(), opts)
	if countErr != nil {
		slog.Warn("contacts.count", "error", countErr)
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"contacts": contacts,
		"total":    total,
		"limit":    opts.Limit,
		"offset":   opts.Offset,
	})
}

func (h *ChannelInstancesHandler) handleResolveContacts(w http.ResponseWriter, r *http.Request) {
	idsParam := r.URL.Query().Get("ids")
	if idsParam == "" {
		writeJSON(w, http.StatusOK, map[string]any{"contacts": map[string]any{}})
		return
	}

	ids := strings.Split(idsParam, ",")
	if len(ids) > 100 {
		ids = ids[:100]
	}

	result, err := h.contactStore.GetContactsBySenderIDs(r.Context(), ids)
	if err != nil {
		slog.Error("contacts.resolve", "error", err)
		locale := store.LocaleFromContext(r.Context())
		writeJSON(w, http.StatusInternalServerError, map[string]string{"error": i18n.T(locale, i18n.MsgFailedToList, "contacts")})
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{"contacts": result})
}

// isValidChannelType checks if the channel type is supported.
func isValidChannelType(ct string) bool {
	switch ct {
	case "telegram", "discord", "slack", "whatsapp", "zalo_oa", "zalo_personal", "feishu":
		return true
	}
	return false
}
