package handlers

import (
	"fmt"
	"net/http"
	"time"

	"git.linuxforward.com/byop/byop-engine/dbstore"
	"git.linuxforward.com/byop/byop-engine/models"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
)

// TicketHandler handles ticket-related operations
type TicketHandler struct {
	Store    dbstore.Store       // Use the defined interface
	Validate *validator.Validate // For request validation
}

// NewTicketHandler creates a new TicketHandler
func NewTicketHandler(store dbstore.Store) *TicketHandler {
	return &TicketHandler{
		Store:    store,
		Validate: validator.New(),
	}
}

// RegisterRoutes registers routes for ticket operations
func (h *TicketHandler) RegisterRoutes(r *gin.RouterGroup) {
	r.GET("/", h.ListTickets)
	r.POST("/", h.CreateTicket)
	r.GET("/:id", h.GetTicket)
	r.PUT("/:id", h.UpdateTicket)
	r.GET("/:id/comments", h.GetTicketComments)
	r.POST("/:id/comments", h.AddTicketComment)
	r.POST("/:id/resolve", h.ResolveTicket)
}

// ListTickets returns all tickets
func (h *TicketHandler) ListTickets(c *gin.Context) {
	ctx := c.Request.Context()
	tickets, err := h.Store.GetTickets(ctx)
	if err != nil {
		appErr := models.NewErrInternalServer("list_tickets_failed", fmt.Errorf("Failed to list tickets: %w", err))
		models.RespondWithError(c, appErr)
		return
	}
	if tickets == nil {
		tickets = []*models.Ticket{} // Return empty slice instead of null
	}
	c.JSON(http.StatusOK, tickets)
}

// CreateTicketInput defines the input for creating a ticket
type CreateTicketInput struct {
	Title       string `json:"title" validate:"required,min=3,max=255"`
	Description string `json:"description" validate:"required,min=10"`
	ClientID    uint   `json:"client_id" validate:"required,gt=0"` // Assuming ClientID is mandatory for a ticket
	UserID      *uint  `json:"user_id,omitempty" validate:"omitempty,gt=0"`
	Priority    string `json:"priority" validate:"omitempty,oneof=low medium high critical"`
}

// CreateTicket creates a new ticket
func (h *TicketHandler) CreateTicket(c *gin.Context) {
	ctx := c.Request.Context()
	var input CreateTicketInput

	if err := c.ShouldBindJSON(&input); err != nil {
		appErr := models.NewErrValidation("invalid_ticket_input", map[string]string{"body": "Invalid request body"}, err)
		models.RespondWithError(c, appErr)
		return
	}

	if err := h.Validate.StructCtx(ctx, input); err != nil {
		errors := models.ExtractValidationErrors(err)
		appErr := models.NewErrValidation("ticket_validation_failed", errors, err)
		models.RespondWithError(c, appErr)
		return
	}

	// authUserID, _ := ctx.Value("userID").(int) // Example: Get authenticated user ID

	ticket := models.Ticket{
		Title:       input.Title,
		Description: input.Description,
		ClientID:    input.ClientID,
		UserID:      input.UserID,
		Status:      models.TicketStatusOpen,
		Priority:    input.Priority,
		// CreatedAt and UpdatedAt will be set by the store or DB
	}
	if ticket.Priority == "" {
		ticket.Priority = models.TicketPriorityMedium // Default priority
	}

	if err := h.Store.CreateTicket(ctx, &ticket); err != nil {
		appErr := models.NewErrInternalServer("create_ticket_failed", fmt.Errorf("Failed to create ticket: %w", err))
		models.RespondWithError(c, appErr)
		return
	}

	c.JSON(http.StatusCreated, ticket)
}

// GetTicket returns a specific ticket
func (h *TicketHandler) GetTicket(c *gin.Context) {
	ctx := c.Request.Context()

	id, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	ticket, err := h.Store.GetTicketByID(ctx, id)
	if err != nil {
		if models.IsErrNotFound(err) {
			appErr := models.NewErrNotFound("ticket_not_found", fmt.Errorf("Ticket with ID %d not found: %w", id, err))
			models.RespondWithError(c, appErr)
			return
		}
		appErr := models.NewErrInternalServer("get_ticket_failed", fmt.Errorf("Failed to get ticket %d: %w", id, err))
		models.RespondWithError(c, appErr)
		return
	}

	c.JSON(http.StatusOK, ticket)
}

// UpdateTicketInput defines the input for updating a ticket
type UpdateTicketInput struct {
	Title       *string `json:"title,omitempty" validate:"omitempty,min=3,max=255"`
	Description *string `json:"description,omitempty" validate:"omitempty,min=10"`
	Priority    *string `json:"priority,omitempty" validate:"omitempty,oneof=low medium high critical"`
	Status      *string `json:"status,omitempty" validate:"omitempty,oneof=open in_progress resolved closed"`
	AssignedTo  *uint   `json:"assigned_to,omitempty" validate:"omitempty,gt=0"`
}

// UpdateTicket updates a ticket
func (h *TicketHandler) UpdateTicket(c *gin.Context) {
	ctx := c.Request.Context()

	id, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	var input UpdateTicketInput
	if err := c.ShouldBindJSON(&input); err != nil {
		appErr := models.NewErrValidation("invalid_update_ticket_input", map[string]string{"body": "Invalid request body"}, err)
		models.RespondWithError(c, appErr)
		return
	}

	if err := h.Validate.StructCtx(ctx, input); err != nil {
		errors := models.ExtractValidationErrors(err)
		appErr := models.NewErrValidation("update_ticket_validation_failed", errors, err)
		models.RespondWithError(c, appErr)
		return
	}

	ticket, err := h.Store.GetTicketByID(ctx, id)
	if err != nil {
		if models.IsErrNotFound(err) {
			appErr := models.NewErrNotFound("ticket_not_found_for_update", fmt.Errorf("Ticket with ID %d not found for update: %w", id, err))
			models.RespondWithError(c, appErr)
			return
		}
		appErr := models.NewErrInternalServer("get_ticket_for_update_failed", fmt.Errorf("Failed to get ticket %d for update: %w", id, err))
		models.RespondWithError(c, appErr)
		return
	}

	// Apply updates
	updated := false
	if input.Title != nil {
		ticket.Title = *input.Title
		updated = true
	}
	if input.Description != nil {
		ticket.Description = *input.Description
		updated = true
	}
	if input.Priority != nil {
		ticket.Priority = *input.Priority
		updated = true
	}
	if input.Status != nil {
		ticket.Status = *input.Status
		updated = true
	}
	if input.AssignedTo != nil {
		ticket.AssignedTo = input.AssignedTo
		updated = true
	}

	if !updated {
		c.JSON(http.StatusOK, ticket) // No changes, return current ticket
		return
	}

	if err := h.Store.UpdateTicket(ctx, ticket); err != nil {
		appErr := models.NewErrInternalServer("update_ticket_failed", fmt.Errorf("Failed to update ticket %d: %w", id, err))
		models.RespondWithError(c, appErr)
		return
	}

	c.JSON(http.StatusOK, ticket)
}

// GetTicketComments returns comments for a ticket
func (h *TicketHandler) GetTicketComments(c *gin.Context) {
	ctx := c.Request.Context()

	ticketID, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	comments, err := h.Store.GetTicketComments(ctx, ticketID)
	if err != nil {
		// If the error indicates the ticket itself was not found, that's a 404 for the ticket.
		// Otherwise, it's an internal error fetching comments.
		// Assuming GetTicketComments might return ErrNotFound if the ticket doesn't exist.
		if models.IsErrNotFound(err) { // This could be ambiguous: ticket not found OR no comments found and store treats it as not found.
			// To be more precise, one might first check if ticket exists, then fetch comments.
			// For now, assume this means ticket itself is not found.
			appErr := models.NewErrNotFound("ticket_not_found_for_comments", fmt.Errorf("Ticket with ID %d not found when fetching comments: %w", ticketID, err))
			models.RespondWithError(c, appErr)
			return
		}
		appErr := models.NewErrInternalServer("get_ticket_comments_failed", fmt.Errorf("Failed to get comments for ticket %d: %w", ticketID, err))
		models.RespondWithError(c, appErr)
		return
	}
	if comments == nil {
		comments = []*models.TicketComment{} // Return empty slice
	}

	c.JSON(http.StatusOK, comments)
}

// AddTicketCommentInput defines the input for adding a comment
type AddTicketCommentInput struct {
	Content string `json:"content" validate:"required,min=1"`
	// UserID will be taken from authenticated user context
}

// AddTicketComment adds a comment to a ticket
func (h *TicketHandler) AddTicketComment(c *gin.Context) {
	ctx := c.Request.Context()

	ticketID, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	var input AddTicketCommentInput
	if err := c.ShouldBindJSON(&input); err != nil {
		appErr := models.NewErrValidation("invalid_comment_input", map[string]string{"body": "Invalid request body for comment"}, err)
		models.RespondWithError(c, appErr)
		return
	}

	if err := h.Validate.StructCtx(ctx, input); err != nil {
		errors := models.ExtractValidationErrors(err)
		appErr := models.NewErrValidation("comment_validation_failed", errors, err)
		models.RespondWithError(c, appErr)
		return
	}

	// Get authenticated user ID (placeholder - replace with actual auth logic)
	authUserID := uint(1) // Example: Assume user ID 1 is authenticated
	// if !ok || authUserID == 0 {
	// 	appErr := models.NewErrUnauthorized("user_not_authenticated_for_comment", fmt.Errorf("User must be authenticated to comment"))
	// 	models.RespondWithError(c, appErr)
	// 	return
	// }

	comment := models.TicketComment{
		TicketID: ticketID,
		UserID:   authUserID, // Set from authenticated user
		Content:  input.Content,
	}

	if err := h.Store.CreateTicketComment(ctx, &comment); err != nil {
		// Check if the error is because the ticket doesn't exist (e.g., foreign key violation)
		if models.IsErrForeignKeyViolation(err) || models.IsErrNotFound(err) { // IsErrNotFound might be returned by store if ticket check fails
			appErr := models.NewErrNotFound("ticket_not_found_for_new_comment", fmt.Errorf("Ticket with ID %d not found, cannot add comment: %w", ticketID, err))
			models.RespondWithError(c, appErr)
			return
		}
		appErr := models.NewErrInternalServer("add_comment_failed", fmt.Errorf("Failed to add comment to ticket %d: %w", ticketID, err))
		models.RespondWithError(c, appErr)
		return
	}

	c.JSON(http.StatusCreated, comment)
}

// ResolveTicket resolves a ticket
func (h *TicketHandler) ResolveTicket(c *gin.Context) {
	ctx := c.Request.Context()

	id, err := parseUintID(c, "id")
	if err != nil {
		models.RespondWithError(c, err)
		return
	}

	ticket, err := h.Store.GetTicketByID(ctx, id)
	if err != nil {
		if models.IsErrNotFound(err) {
			appErr := models.NewErrNotFound("ticket_not_found_for_resolve", fmt.Errorf("Ticket with ID %d not found for resolve: %w", id, err))
			models.RespondWithError(c, appErr)
			return
		}
		appErr := models.NewErrInternalServer("get_ticket_for_resolve_failed", fmt.Errorf("Failed to get ticket %d for resolve: %w", id, err))
		models.RespondWithError(c, appErr)
		return
	}

	if ticket.Status == models.TicketStatusResolved || ticket.Status == models.TicketStatusClosed {
		appErr := models.NewErrValidation("ticket_already_resolved_or_closed", map[string]string{"status": fmt.Sprintf("Ticket is already %s", ticket.Status)}, nil)
		models.RespondWithError(c, appErr)
		return
	}

	ticket.Status = models.TicketStatusResolved
	now := time.Now()
	ticket.ResolvedAt = &now

	if err := h.Store.UpdateTicket(ctx, ticket); err != nil {
		appErr := models.NewErrInternalServer("resolve_ticket_failed", fmt.Errorf("Failed to resolve ticket %d: %w", id, err))
		models.RespondWithError(c, appErr)
		return
	}

	c.JSON(http.StatusOK, ticket)
}
