// telegrambots.go — HTTP handlers for the /v1/tenants/{id}/telegram/bots/*
// routes (M13b W3).
//
// Routes (all require super_admin role per the existing
// canManageTelegram scope):
//
//   GET    /v1/tenants/{id}/telegram/bots                       — list
//   POST   /v1/tenants/{id}/telegram/bots                       — create
//   GET    /v1/tenants/{id}/telegram/bots/{bid}                 — detail
//   PATCH  /v1/tenants/{id}/telegram/bots/{bid}                 — update
//   POST   /v1/tenants/{id}/telegram/bots/{bid}/status          — set status
//   POST   /v1/tenants/{id}/telegram/bots/{bid}/rotate-token    — rotate token
//
// Errors:
//   400 — bad input (validation, JSON parse, bad UUID, bad bot id)
//   401 — handled by RequireAuth middleware (no body rewrite here)
//   403 — caller is not super_admin (RequireRole gate)
//   404 — tenant or bot id not found
//   409 — duplicate bot id on create
//   500 — unexpected DB error
//
// The plaintext bot_token is NEVER returned in any response.
// The wire shape is the authd.TelegramBot struct, which exposes
// `bot_token_set: bool` instead.

package main

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

	"git3.techno-world.net/lrosales/broad-announce/internal/authd"
)

// telegramBotsListResponse is the wire shape for GET
// /v1/tenants/{id}/telegram/bots.
type telegramBotsListResponse struct {
	Items  []authd.TelegramBot `json:"items"`
	Total  int                 `json:"total"`
	Limit  int                 `json:"limit"`
	Offset int                 `json:"offset"`
}

// listTelegramBotsHandler wires GET /v1/tenants/{id}/telegram/bots.
func listTelegramBotsHandler(ad *authd.Authd, logger *slog.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		claims := authd.ClaimsFromContext(r.Context())
		if claims == nil {
			writeErr(w, http.StatusUnauthorized, "unauthorized", "claims missing")
			return
		}
		tenantID := r.PathValue("id")
		if !isUUID(tenantID) {
			writeErr(w, http.StatusBadRequest, "bad_request", "tenant id must be a UUID")
			return
		}
		q := strings.TrimSpace(r.URL.Query().Get("q"))
		statusFilter := strings.TrimSpace(r.URL.Query().Get("status"))
		limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
		offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
		filter := authd.TelegramBotFilter{
			CompanyID: tenantID,
			Q:         q,
			Status:    statusFilter,
			Limit:     limit,
			Offset:    offset,
		}
		items, total, err := ad.Store().ListTelegramBots(r.Context(), filter)
		if err != nil {
			logger.Error("list telegram_bots", "err", err, "actor", claims.UserID, "tenant_id", tenantID)
			writeErr(w, http.StatusInternalServerError, "internal", "list failed")
			return
		}
		if filter.Limit <= 0 {
			filter.Limit = 100
		}
		if filter.Limit > 500 {
			filter.Limit = 500
		}
		writeJSON(w, http.StatusOK, telegramBotsListResponse{
			Items: items, Total: total, Limit: filter.Limit, Offset: filter.Offset,
		})
	}
}

// createTelegramBotRequest is the POST body. bot_token is
// REQUIRED on create (the operator got it from @BotFather).
// All other fields optional.
type createTelegramBotRequest struct {
	ID              string `json:"id"`
	Name            string `json:"name"`
	BotToken        string `json:"bot_token"`
	WelcomeMessage  string `json:"welcome_message"`
	DefaultSourceID string `json:"default_source_id"`
	Description     string `json:"description"`
}

// createTelegramBotHandler wires POST /v1/tenants/{id}/telegram/bots.
func createTelegramBotHandler(ad *authd.Authd, logger *slog.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		claims := authd.ClaimsFromContext(r.Context())
		if claims == nil {
			writeErr(w, http.StatusUnauthorized, "unauthorized", "claims missing")
			return
		}
		tenantID := r.PathValue("id")
		if !isUUID(tenantID) {
			writeErr(w, http.StatusBadRequest, "bad_request", "tenant id must be a UUID")
			return
		}
		// W3 gates telegram to super_admin only (see canManageTelegram
		// in web/src/lib/scope.ts). RequireRole is wired in main.go;
		// this is belt-and-suspenders.
		if claims.Role != "super_admin" {
			writeErr(w, http.StatusForbidden, "forbidden", "super_admin only")
			return
		}
		var req createTelegramBotRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
			return
		}
		in := authd.CreateTelegramBotInput{
			ID:              strings.TrimSpace(req.ID),
			Name:            strings.TrimSpace(req.Name),
			BotToken:        strings.TrimSpace(req.BotToken),
			WelcomeMessage:  req.WelcomeMessage,
			DefaultSourceID: strings.TrimSpace(req.DefaultSourceID),
			Description:     req.Description,
		}
		// Look up the auth tenant's display name so the
		// bridge INSERT into public.companies (needed because
		// telegram_bots.company_id FKs into public.companies,
		// not auth.tenants) has a sensible name value.
		tenant, err := ad.Store().GetTenant(r.Context(), tenantID)
		if err != nil {
			if errors.Is(err, authd.ErrTenantNotFound) {
				writeErr(w, http.StatusNotFound, "not_found", "tenant not found")
				return
			}
			logger.Error("create telegram_bot: lookup tenant", "err", err, "tenant_id", tenantID)
			writeErr(w, http.StatusInternalServerError, "internal", "lookup failed")
			return
		}
		ip := clientIP(r)
		ua := r.UserAgent()
		bot, err := ad.Store().CreateTelegramBot(r.Context(), tenantID, tenant.DisplayName, in, claims.UserID, ip, ua)
		if err != nil {
			switch {
			case errors.Is(err, authd.ErrTelegramBotIDTaken):
				writeErr(w, http.StatusConflict, "id_taken", "telegram bot id already in use")
			case errors.Is(err, authd.ErrTelegramBotInvalid):
				writeErr(w, http.StatusBadRequest, "invalid", err.Error())
			default:
				logger.Error("create telegram_bot", "err", err, "tenant_id", tenantID, "actor", claims.UserID)
				writeErr(w, http.StatusInternalServerError, "internal", "create failed")
			}
			return
		}
		logger.Info("telegram_bot created",
			"tenant_id", tenantID, "bot_id", bot.ID, "actor", claims.UserID)
		writeJSON(w, http.StatusCreated, bot)
	}
}

// getTelegramBotHandler wires GET /v1/tenants/{id}/telegram/bots/{bid}.
func getTelegramBotHandler(ad *authd.Authd, logger *slog.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		claims := authd.ClaimsFromContext(r.Context())
		if claims == nil {
			writeErr(w, http.StatusUnauthorized, "unauthorized", "claims missing")
			return
		}
		tenantID := r.PathValue("id")
		if !isUUID(tenantID) {
			writeErr(w, http.StatusBadRequest, "bad_request", "tenant id must be a UUID")
			return
		}
		if claims.Role != "super_admin" {
			writeErr(w, http.StatusForbidden, "forbidden", "super_admin only")
			return
		}
		botID := r.PathValue("bid")
		if botID == "" {
			writeErr(w, http.StatusBadRequest, "bad_request", "bot id is required")
			return
		}
		bot, err := ad.Store().GetTelegramBot(r.Context(), tenantID, botID)
		if err != nil {
			if errors.Is(err, authd.ErrTelegramBotNotFound) {
				writeErr(w, http.StatusNotFound, "not_found", "telegram bot not found")
				return
			}
			logger.Error("get telegram_bot", "err", err, "tenant_id", tenantID, "bot_id", botID)
			writeErr(w, http.StatusInternalServerError, "internal", "lookup failed")
			return
		}
		writeJSON(w, http.StatusOK, bot)
	}
}

// updateTelegramBotRequest is the PATCH body. bot_token is
// NOT updatable here — use POST .../rotate-token.
type updateTelegramBotRequest struct {
	Name            *string `json:"name"`
	WelcomeMessage  *string `json:"welcome_message"`
	DefaultSourceID *string `json:"default_source_id"`
	Description     *string `json:"description"`
}

// updateTelegramBotHandler wires PATCH /v1/tenants/{id}/telegram/bots/{bid}.
func updateTelegramBotHandler(ad *authd.Authd, logger *slog.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		claims := authd.ClaimsFromContext(r.Context())
		if claims == nil {
			writeErr(w, http.StatusUnauthorized, "unauthorized", "claims missing")
			return
		}
		tenantID := r.PathValue("id")
		if !isUUID(tenantID) {
			writeErr(w, http.StatusBadRequest, "bad_request", "tenant id must be a UUID")
			return
		}
		if claims.Role != "super_admin" {
			writeErr(w, http.StatusForbidden, "forbidden", "super_admin only")
			return
		}
		botID := r.PathValue("bid")
		if botID == "" {
			writeErr(w, http.StatusBadRequest, "bad_request", "bot id is required")
			return
		}
		var req updateTelegramBotRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
			return
		}
		in := authd.UpdateTelegramBotInput{
			Name:            req.Name,
			WelcomeMessage:  req.WelcomeMessage,
			DefaultSourceID: req.DefaultSourceID,
			Description:     req.Description,
		}
		ip := clientIP(r)
		ua := r.UserAgent()
		bot, err := ad.Store().UpdateTelegramBot(r.Context(), tenantID, botID, in, claims.UserID, ip, ua)
		if err != nil {
			switch {
			case errors.Is(err, authd.ErrTelegramBotNotFound):
				writeErr(w, http.StatusNotFound, "not_found", "telegram bot not found")
			case errors.Is(err, authd.ErrTelegramBotInvalid):
				writeErr(w, http.StatusBadRequest, "invalid", err.Error())
			default:
				logger.Error("update telegram_bot", "err", err, "tenant_id", tenantID, "bot_id", botID, "actor", claims.UserID)
				writeErr(w, http.StatusInternalServerError, "internal", "update failed")
			}
			return
		}
		logger.Info("telegram_bot updated",
			"tenant_id", tenantID, "bot_id", botID, "actor", claims.UserID)
		writeJSON(w, http.StatusOK, bot)
	}
}

// setTelegramBotStatusRequest is the POST /status body.
type setTelegramBotStatusRequest struct {
	Status string `json:"status"`
}

// setTelegramBotStatusHandler wires POST /v1/tenants/{id}/telegram/bots/{bid}/status.
func setTelegramBotStatusHandler(ad *authd.Authd, logger *slog.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		claims := authd.ClaimsFromContext(r.Context())
		if claims == nil {
			writeErr(w, http.StatusUnauthorized, "unauthorized", "claims missing")
			return
		}
		tenantID := r.PathValue("id")
		if !isUUID(tenantID) {
			writeErr(w, http.StatusBadRequest, "bad_request", "tenant id must be a UUID")
			return
		}
		if claims.Role != "super_admin" {
			writeErr(w, http.StatusForbidden, "forbidden", "super_admin only")
			return
		}
		botID := r.PathValue("bid")
		if botID == "" {
			writeErr(w, http.StatusBadRequest, "bad_request", "bot id is required")
			return
		}
		var req setTelegramBotStatusRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
			return
		}
		ip := clientIP(r)
		ua := r.UserAgent()
		bot, err := ad.Store().SetTelegramBotStatus(r.Context(), tenantID, botID, strings.TrimSpace(req.Status), claims.UserID, ip, ua)
		if err != nil {
			switch {
			case errors.Is(err, authd.ErrTelegramBotNotFound):
				writeErr(w, http.StatusNotFound, "not_found", "telegram bot not found")
			case errors.Is(err, authd.ErrTelegramBotInvalid):
				writeErr(w, http.StatusBadRequest, "invalid", err.Error())
			default:
				logger.Error("set telegram_bot status", "err", err, "tenant_id", tenantID, "bot_id", botID, "actor", claims.UserID)
				writeErr(w, http.StatusInternalServerError, "internal", "update failed")
			}
			return
		}
		logger.Info("telegram_bot status changed",
			"tenant_id", tenantID, "bot_id", botID, "to", bot.Status, "actor", claims.UserID)
		writeJSON(w, http.StatusOK, bot)
	}
}

// rotateTelegramBotTokenRequest is the POST /rotate-token body.
// bot_token is REQUIRED (the operator got a new one from
// @BotFather and is pasting it in).
type rotateTelegramBotTokenRequest struct {
	BotToken string `json:"bot_token"`
}

// rotateTelegramBotTokenHandler wires POST
// /v1/tenants/{id}/telegram/bots/{bid}/rotate-token.
//
// The new bot_token replaces the existing one in the DB and is
// bcrypt-hashed for the bot_token_hash column. The response
// does NOT include the plaintext (the operator already has it;
// the server doesn't echo it back).
func rotateTelegramBotTokenHandler(ad *authd.Authd, logger *slog.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		claims := authd.ClaimsFromContext(r.Context())
		if claims == nil {
			writeErr(w, http.StatusUnauthorized, "unauthorized", "claims missing")
			return
		}
		tenantID := r.PathValue("id")
		if !isUUID(tenantID) {
			writeErr(w, http.StatusBadRequest, "bad_request", "tenant id must be a UUID")
			return
		}
		if claims.Role != "super_admin" {
			writeErr(w, http.StatusForbidden, "forbidden", "super_admin only")
			return
		}
		botID := r.PathValue("bid")
		if botID == "" {
			writeErr(w, http.StatusBadRequest, "bad_request", "bot id is required")
			return
		}
		var req rotateTelegramBotTokenRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			writeErr(w, http.StatusBadRequest, "bad_request", "invalid JSON body")
			return
		}
		ip := clientIP(r)
		ua := r.UserAgent()
		bot, err := ad.Store().RotateTelegramBotToken(r.Context(), tenantID, botID, strings.TrimSpace(req.BotToken), claims.UserID, ip, ua)
		if err != nil {
			switch {
			case errors.Is(err, authd.ErrTelegramBotNotFound):
				writeErr(w, http.StatusNotFound, "not_found", "telegram bot not found")
			case errors.Is(err, authd.ErrTelegramBotInvalid):
				writeErr(w, http.StatusBadRequest, "invalid", err.Error())
			default:
				logger.Error("rotate telegram_bot token", "err", err, "tenant_id", tenantID, "bot_id", botID, "actor", claims.UserID)
				writeErr(w, http.StatusInternalServerError, "internal", "rotate failed")
			}
			return
		}
		logger.Info("telegram_bot token rotated",
			"tenant_id", tenantID, "bot_id", botID, "actor", claims.UserID)
		writeJSON(w, http.StatusOK, bot)
	}
}
