package handlers

import (
	"errors"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/auth"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/service"
	"github.com/orchard9/rdev/internal/validate"
	"github.com/orchard9/rdev/pkg/api"
)

// ConversationsHandler handles conversation endpoints.
type ConversationsHandler struct {
	conversationService *service.ConversationService
}

// NewConversationsHandler creates a new conversations handler.
func NewConversationsHandler(conversationService *service.ConversationService) *ConversationsHandler {
	return &ConversationsHandler{
		conversationService: conversationService,
	}
}

// Mount registers conversation routes.
func (h *ConversationsHandler) Mount(r api.Router) {
	r.Route("/projects/{id}/conversations", func(r chi.Router) {
		// Read operations
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/", h.ListConversations)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/{conversationId}", h.GetConversation)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/{conversationId}/messages", h.GetMessages)

		// Write operations
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Post("/", h.CreateConversation)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Patch("/{conversationId}", h.UpdateConversation)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Delete("/{conversationId}", h.DeleteConversation)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Post("/{conversationId}/messages", h.AddMessage)
	})
}

// CreateConversationRequest is the request body for POST /projects/{id}/conversations.
type CreateConversationRequest struct {
	Title string `json:"title"`
}

// CreateConversation creates a new conversation.
// POST /projects/{id}/conversations
func (h *ConversationsHandler) CreateConversation(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")

	var req CreateConversationRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	conv, err := h.conversationService.CreateConversation(r.Context(), projectID, req.Title)
	if err != nil {
		api.WriteInternalError(w, r, "failed to create conversation")
		return
	}

	api.WriteCreated(w, r, toConversationDTO(conv))
}

// ListConversations returns all conversations for a project.
// GET /projects/{id}/conversations
func (h *ConversationsHandler) ListConversations(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")

	convs, err := h.conversationService.ListConversations(r.Context(), projectID)
	if err != nil {
		api.WriteInternalError(w, r, "failed to list conversations")
		return
	}

	dtos := make([]*ConversationDTO, len(convs))
	for i, c := range convs {
		dtos[i] = toConversationDTO(c)
	}

	api.WriteSuccess(w, r, map[string]any{
		"conversations": dtos,
		"total":         len(dtos),
	})
}

// GetConversation retrieves a conversation with all messages.
// GET /projects/{id}/conversations/{conversationId}
func (h *ConversationsHandler) GetConversation(w http.ResponseWriter, r *http.Request) {
	conversationID := domain.ConversationID(chi.URLParam(r, "conversationId"))

	conv, err := h.conversationService.GetConversationWithMessages(r.Context(), conversationID)
	if err != nil {
		if errors.Is(err, domain.ErrConversationNotFound) {
			api.WriteNotFound(w, r, "conversation not found")
			return
		}
		api.WriteInternalError(w, r, "failed to get conversation")
		return
	}

	api.WriteSuccess(w, r, toConversationWithMessagesDTO(conv))
}

// UpdateConversationRequest is the request body for PATCH /projects/{id}/conversations/{conversationId}.
type UpdateConversationRequest struct {
	Title string `json:"title"`
}

// UpdateConversation updates conversation metadata.
// PATCH /projects/{id}/conversations/{conversationId}
func (h *ConversationsHandler) UpdateConversation(w http.ResponseWriter, r *http.Request) {
	conversationID := domain.ConversationID(chi.URLParam(r, "conversationId"))

	var req UpdateConversationRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	v := validate.New()
	v.Required(req.Title, "title")
	if err := v.Error(); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if err := h.conversationService.UpdateTitle(r.Context(), conversationID, req.Title); err != nil {
		if errors.Is(err, domain.ErrConversationNotFound) {
			api.WriteNotFound(w, r, "conversation not found")
			return
		}
		api.WriteInternalError(w, r, "failed to update conversation")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"message": "conversation updated",
	})
}

// DeleteConversation deletes a conversation.
// DELETE /projects/{id}/conversations/{conversationId}
func (h *ConversationsHandler) DeleteConversation(w http.ResponseWriter, r *http.Request) {
	conversationID := domain.ConversationID(chi.URLParam(r, "conversationId"))

	if err := h.conversationService.DeleteConversation(r.Context(), conversationID); err != nil {
		if errors.Is(err, domain.ErrConversationNotFound) {
			api.WriteNotFound(w, r, "conversation not found")
			return
		}
		api.WriteInternalError(w, r, "failed to delete conversation")
		return
	}

	api.WriteSuccess(w, r, map[string]any{
		"message": "conversation deleted",
	})
}

// AddMessageRequest is the request body for POST /projects/{id}/conversations/{conversationId}/messages.
type AddMessageRequest struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

// AddMessage adds a message to a conversation.
// POST /projects/{id}/conversations/{conversationId}/messages
func (h *ConversationsHandler) AddMessage(w http.ResponseWriter, r *http.Request) {
	conversationID := domain.ConversationID(chi.URLParam(r, "conversationId"))

	var req AddMessageRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	v := validate.New()
	v.Required(req.Role, "role")
	v.Required(req.Content, "content")
	if err := v.Error(); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	role := domain.MessageRole(req.Role)
	if role != domain.MessageRoleUser && role != domain.MessageRoleAssistant && role != domain.MessageRoleSystem {
		api.WriteBadRequest(w, r, "role must be 'user', 'assistant', or 'system'")
		return
	}

	msg, err := h.conversationService.AddMessage(r.Context(), conversationID, role, req.Content)
	if err != nil {
		if errors.Is(err, domain.ErrConversationNotFound) {
			api.WriteNotFound(w, r, "conversation not found")
			return
		}
		api.WriteInternalError(w, r, "failed to add message")
		return
	}

	api.WriteCreated(w, r, toMessageDTO(msg))
}

// GetMessages retrieves all messages for a conversation.
// GET /projects/{id}/conversations/{conversationId}/messages
func (h *ConversationsHandler) GetMessages(w http.ResponseWriter, r *http.Request) {
	conversationID := domain.ConversationID(chi.URLParam(r, "conversationId"))

	messages, err := h.conversationService.GetMessages(r.Context(), conversationID)
	if err != nil {
		api.WriteInternalError(w, r, "failed to get messages")
		return
	}

	dtos := make([]*MessageDTO, len(messages))
	for i, m := range messages {
		dtos[i] = toMessageDTO(m)
	}

	api.WriteSuccess(w, r, map[string]any{
		"messages": dtos,
		"total":    len(dtos),
	})
}
