package main

import (
	"net/http"
	"strings"

	"fn-registry/functions/infra"
)

const maxBodyBytes = 1 << 20 // 1 MiB

func badRequest(w http.ResponseWriter, msg string) {
	infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusBadRequest, Code: "bad_request", Message: msg})
}

func notFound(w http.ResponseWriter, msg string) {
	infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusNotFound, Code: "not_found", Message: msg})
}

func serverError(w http.ResponseWriter, err error) {
	infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusInternalServerError, Code: "internal", Message: err.Error()})
}

// GET /api/board → { columns: [...], cards: [...] }
func handleGetBoard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cols, err := db.ListColumns()
		if err != nil {
			serverError(w, err)
			return
		}
		cards, err := db.ListCardsWithTime()
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, map[string]any{
			"columns": cols,
			"cards":   cards,
		})
	}
}

// POST /api/columns { name }
func handleCreateColumn(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var body struct{ Name string `json:"name"` }
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if strings.TrimSpace(body.Name) == "" {
			badRequest(w, "name required")
			return
		}
		c, err := db.CreateColumn(body.Name)
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusCreated, c)
	}
}

// PATCH /api/columns/{id} { name?, position?, location?, width? }
func handleUpdateColumn(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		var body struct {
			Name           *string `json:"name"`
			Position       *int    `json:"position"`
			Location       *string `json:"location"`
			Width          *int    `json:"width"`
			WIPLimit       *int    `json:"wip_limit"`
			IsDone         *bool   `json:"is_done"`
			MaxTimeMinutes *int    `json:"max_time_minutes"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if err := db.UpdateColumn(id, ColumnPatch{Name: body.Name, Position: body.Position, Location: body.Location, Width: body.Width, WIPLimit: body.WIPLimit, IsDone: body.IsDone, MaxTimeMinutes: body.MaxTimeMinutes}); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// DELETE /api/columns/{id}
func handleDeleteColumn(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		if err := db.DeleteColumn(id); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// POST /api/columns/reorder { ids: [...] }
func handleReorderColumns(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var body struct{ IDs []string `json:"ids"` }
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if err := db.ReorderColumns(body.IDs); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// POST /api/cards { column_id, requester?, title, description? }
func handleCreateCard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var body struct {
			ColumnID    string   `json:"column_id"`
			Requester   string   `json:"requester"`
			Title       string   `json:"title"`
			Description string   `json:"description"`
			AssigneeID  *string  `json:"assignee_id"`
			Tags        []string `json:"tags"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if body.ColumnID == "" || strings.TrimSpace(body.Title) == "" {
			badRequest(w, "column_id and title required")
			return
		}
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		c, err := db.CreateCard(body.ColumnID, body.Requester, body.Title, body.Description, actor)
		if err == nil && body.AssigneeID != nil && *body.AssigneeID != "" {
			err = db.UpdateCardWithActor(c.ID, CardPatch{AssigneeID: body.AssigneeID, HasAssignee: true}, actor)
			if err == nil {
				c.AssigneeID = body.AssigneeID
			}
		}
		if err == nil && len(body.Tags) > 0 {
			tags := body.Tags
			err = db.UpdateCardWithActor(c.ID, CardPatch{Tags: &tags}, actor)
			if err == nil {
				c.Tags = tags
			}
		}
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusCreated, c)
	}
}

// PATCH /api/cards/{id} { requester?, title?, description?, color? }
func handleUpdateCard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		var raw map[string]any
		if err := infra.HTTPParseBody(r, &raw, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		patch := CardPatch{}
		if v, ok := raw["requester"].(string); ok {
			patch.Requester = &v
		}
		if v, ok := raw["title"].(string); ok {
			patch.Title = &v
		}
		if v, ok := raw["description"].(string); ok {
			patch.Description = &v
		}
		if v, ok := raw["color"].(string); ok {
			patch.Color = &v
		}
		if v, ok := raw["locked"].(bool); ok {
			patch.Locked = &v
		}
		if v, present := raw["assignee_id"]; present {
			patch.HasAssignee = true
			if v == nil {
				empty := ""
				patch.AssigneeID = &empty
			} else if s, ok := v.(string); ok {
				patch.AssigneeID = &s
			}
		}
		if v, present := raw["deadline"]; present {
			patch.HasDeadline = true
			if v == nil {
				empty := ""
				patch.Deadline = &empty
			} else if s, ok := v.(string); ok {
				patch.Deadline = &s
			}
		}
		if v, present := raw["tags"]; present {
			tags := []string{}
			if arr, ok := v.([]any); ok {
				for _, t := range arr {
					if s, ok := t.(string); ok {
						tags = append(tags, s)
					}
				}
			}
			patch.Tags = &tags
		}
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		if err := db.UpdateCardWithActor(id, patch, actor); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// PUT /api/cards/{id}/stickers { stickers: [{emoji,x,y}, ...] }
func handleUpdateCardStickers(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		var body struct {
			Stickers []Sticker `json:"stickers"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if err := db.UpdateStickers(id, body.Stickers); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// DELETE /api/cards/{id}
func handleDeleteCard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		if err := db.DeleteCardWithActor(id, actor); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// POST /api/cards/{id}/move { column_id, ordered_ids }
func handleMoveCard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		var body struct {
			ColumnID   string   `json:"column_id"`
			OrderedIDs []string `json:"ordered_ids"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if body.ColumnID == "" {
			badRequest(w, "column_id required")
			return
		}
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		if err := db.MoveCard(id, body.ColumnID, body.OrderedIDs, actor); err != nil {
			if strings.Contains(err.Error(), "not found") {
				notFound(w, "card not found")
				return
			}
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// GET /api/cards/{id}/messages → [CardMessage, ...]
func handleListCardMessages(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		msgs, err := db.ListCardMessages(id)
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, msgs)
	}
}

// POST /api/cards/{id}/messages { body }
func handleCreateCardMessage(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		var body struct {
			Body string `json:"body"`
		}
		if err := infra.HTTPParseBody(r, &body, maxBodyBytes); err != nil {
			badRequest(w, err.Error())
			return
		}
		if strings.TrimSpace(body.Body) == "" {
			badRequest(w, "body required")
			return
		}
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		if actor == "" {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "unauthorized", Message: "session required"})
			return
		}
		m, err := db.CreateCardMessage(id, actor, body.Body)
		if err != nil {
			if strings.Contains(err.Error(), "not found") {
				notFound(w, err.Error())
				return
			}
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusCreated, m)
	}
}

// DELETE /api/cards/{cid}/messages/{mid}
func handleDeleteCardMessage(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		mid := r.PathValue("mid")
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		if actor == "" {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: http.StatusUnauthorized, Code: "unauthorized", Message: "session required"})
			return
		}
		if err := db.DeleteCardMessage(mid, actor); err != nil {
			if strings.Contains(err.Error(), "not found") {
				notFound(w, err.Error())
				return
			}
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// POST /api/cards/{id}/duplicate
func handleDuplicateCard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		c, err := db.DuplicateCard(id, actor)
		if err != nil {
			if strings.Contains(err.Error(), "not found") {
				notFound(w, "card not found")
				return
			}
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusCreated, c)
	}
}

// GET /api/cards/{id}/history → [HistoryEntry, ...]
func handleCardHistory(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		entries, err := db.CardHistory(id)
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, entries)
	}
}

// GET /api/trash
func handleListTrash(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		cards, err := db.ListDeletedCards()
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, cards)
	}
}

// POST /api/cards/{id}/restore
func handleRestoreCard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		actor, _ := infra.UserIDFromContext(r.Context(), userCtxKey)
		if err := db.RestoreCardWithActor(id, actor); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

// DELETE /api/cards/{id}/purge
func handlePurgeCard(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		if err := db.PurgeCard(id); err != nil {
			serverError(w, err)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	}
}

func apiRoutes(db *DB, chatWorkdir string, logger *ChatLogger, internalToken string, flags *FeatureFlags) []infra.Route {
	return []infra.Route{
		{Method: "GET", Path: "/api/flags", Handler: handleListFlags(flags)},
		{Method: "POST", Path: "/api/auth/register", Handler: handleRegister(db, flags)},
		{Method: "POST", Path: "/api/auth/login", Handler: handleLogin(db)},
		{Method: "POST", Path: "/api/auth/logout", Handler: handleLogout(db)},
		{Method: "GET", Path: "/api/me", Handler: handleMe(db)},
		{Method: "PATCH", Path: "/api/me", Handler: handlePatchMe(db)},
		{Method: "GET", Path: "/api/users", Handler: handleListUsers(db)},
		{Method: "GET", Path: "/api/board", Handler: handleGetBoard(db)},
		{Method: "POST", Path: "/api/columns", Handler: handleCreateColumn(db)},
		{Method: "POST", Path: "/api/columns/reorder", Handler: handleReorderColumns(db)},
		{Method: "PATCH", Path: "/api/columns/{id}", Handler: handleUpdateColumn(db)},
		{Method: "DELETE", Path: "/api/columns/{id}", Handler: handleDeleteColumn(db)},
		{Method: "POST", Path: "/api/cards", Handler: handleCreateCard(db)},
		{Method: "PATCH", Path: "/api/cards/{id}", Handler: handleUpdateCard(db)},
		{Method: "PUT", Path: "/api/cards/{id}/stickers", Handler: handleUpdateCardStickers(db)},
		{Method: "DELETE", Path: "/api/cards/{id}", Handler: handleDeleteCard(db)},
		{Method: "POST", Path: "/api/cards/{id}/move", Handler: handleMoveCard(db)},
		{Method: "POST", Path: "/api/cards/{id}/duplicate", Handler: handleDuplicateCard(db)},
		{Method: "GET", Path: "/api/cards/{id}/messages", Handler: handleListCardMessages(db)},
		{Method: "POST", Path: "/api/cards/{id}/messages", Handler: handleCreateCardMessage(db)},
		{Method: "DELETE", Path: "/api/cards/{id}/messages/{mid}", Handler: handleDeleteCardMessage(db)},
		{Method: "GET", Path: "/api/cards/{id}/history", Handler: handleCardHistory(db)},
		{Method: "GET", Path: "/api/trash", Handler: handleListTrash(db)},
		{Method: "POST", Path: "/api/cards/{id}/restore", Handler: handleRestoreCard(db)},
		{Method: "DELETE", Path: "/api/cards/{id}/purge", Handler: handlePurgeCard(db)},
		{Method: "POST", Path: "/api/chat", Handler: handleChat(db, chatWorkdir, logger)},
		{Method: "GET", Path: "/api/chat/ws", Handler: handleChatWS(db, chatWorkdir, logger, internalToken)},
		{Method: "POST", Path: "/api/tool/{name}", Handler: handleInternalTool(db, internalToken, logger)},
		{Method: "GET", Path: "/api/metrics", Handler: handleMetrics(db)},
		{Method: "GET", Path: "/api/tags", Handler: handleListTags(db)},
		{Method: "GET", Path: "/api/requesters", Handler: handleListRequesters(db)},
	}
}

func handleListTags(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tags, err := db.ListAllTags()
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, tags)
	}
}

func handleListRequesters(db *DB) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		out, err := db.ListDistinctRequesters()
		if err != nil {
			serverError(w, err)
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, out)
	}
}
