package handlers

import (
	"Yimaru-Backend/internal/domain"
	"context"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/gofiber/fiber/v2"
)

type createIssueReq struct {
	Subject     string                 `json:"subject" validate:"required"`
	Description string                 `json:"description" validate:"required"`
	IssueType   string                 `json:"issue_type" validate:"required"`
	Metadata    map[string]interface{} `json:"metadata,omitempty"`
}

type issueRes struct {
	ID          int64                  `json:"id"`
	UserID      int64                  `json:"user_id"`
	UserRole    string                 `json:"user_role"`
	Subject     string                 `json:"subject"`
	Description string                 `json:"description"`
	IssueType   string                 `json:"issue_type"`
	Status      string                 `json:"status"`
	Metadata    map[string]interface{} `json:"metadata,omitempty"`
	CreatedAt   string                 `json:"created_at"`
	UpdatedAt   string                 `json:"updated_at"`
}

type updateIssueStatusReq struct {
	Status string `json:"status" validate:"required,oneof=pending in_progress resolved rejected"`
}

type issueListRes struct {
	Issues     []issueRes `json:"issues"`
	TotalCount int64      `json:"total_count"`
}

func mapIssueToRes(issue domain.ReportedIssue) issueRes {
	return issueRes{
		ID:          issue.ID,
		UserID:      issue.UserID,
		UserRole:    string(issue.UserRole),
		Subject:     issue.Subject,
		Description: issue.Description,
		IssueType:   string(issue.IssueType),
		Status:      string(issue.Status),
		Metadata:    issue.Metadata,
		CreatedAt:   issue.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
		UpdatedAt:   issue.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
	}
}

// CreateIssue godoc
// @Summary Report an issue
// @Description Allows any authenticated user to report an issue they encountered
// @Tags issues
// @Accept json
// @Produce json
// @Security Bearer
// @Param body body createIssueReq true "Issue report payload"
// @Success 201 {object} domain.Response{data=issueRes}
// @Failure 400 {object} domain.ErrorResponse
// @Failure 401 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues [post]
func (h *Handler) CreateIssue(c *fiber.Ctx) error {
	var req createIssueReq
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Invalid request body",
			Error:   err.Error(),
		})
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		var errMsg string
		for field, msg := range valErrs {
			errMsg += fmt.Sprintf("%s: %s; ", field, msg)
		}
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Validation failed",
			Error:   errMsg,
		})
	}

	userID := c.Locals("user_id").(int64)
	role := c.Locals("role").(domain.Role)

	issueReq := domain.ReportedIssueReq{
		Subject:     req.Subject,
		Description: req.Description,
		IssueType:   domain.ReportedIssueType(req.IssueType),
		Metadata:    req.Metadata,
	}

	issue, err := h.issueReportingSvc.CreateReportedIssue(c.Context(), issueReq, userID, role)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
			Message: "Failed to create issue report",
			Error:   err.Error(),
		})
	}

	actorRole := string(role)
	ip := c.IP()
	ua := c.Get("User-Agent")
	meta, _ := json.Marshal(map[string]interface{}{"subject": issue.Subject, "issue_type": string(issue.IssueType)})
	go h.activityLogSvc.RecordAction(context.Background(), &userID, &actorRole, domain.ActionIssueCreated, domain.ResourceIssue, &issue.ID, "Reported issue: "+issue.Subject, meta, &ip, &ua)

	go func() {
		admins, _, err := h.userSvc.GetAllUsers(context.Background(), domain.UserFilter{Role: string(domain.RoleAdmin)})
		if err != nil {
			return
		}
		for _, admin := range admins {
			h.sendInAppNotification(admin.ID, domain.NOTIFICATION_TYPE_ISSUE_CREATED, "New Issue Reported", "A new issue \""+issue.Subject+"\" has been reported.")
		}
	}()

	return c.Status(fiber.StatusCreated).JSON(domain.Response{
		Message: "Issue reported successfully",
		Data:    mapIssueToRes(issue),
		Success: true,
	})
}

// GetIssueByID godoc
// @Summary Get issue by ID
// @Description Returns a single issue report by its ID (admin only)
// @Tags issues
// @Produce json
// @Security Bearer
// @Param id path int true "Issue ID"
// @Success 200 {object} domain.Response{data=issueRes}
// @Failure 400 {object} domain.ErrorResponse
// @Failure 404 {object} domain.ErrorResponse
// @Router /api/v1/issues/{id} [get]
func (h *Handler) GetIssueByID(c *fiber.Ctx) error {
	idStr := c.Params("id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Invalid issue ID",
			Error:   err.Error(),
		})
	}

	issue, err := h.issueReportingSvc.GetIssueByID(c.Context(), id)
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
			Message: "Issue not found",
			Error:   err.Error(),
		})
	}

	return c.JSON(domain.Response{
		Message: "Issue retrieved successfully",
		Data:    mapIssueToRes(issue),
		Success: true,
	})
}

// GetUserIssues godoc
// @Summary Get issues for a specific user
// @Description Returns paginated issues reported by a specific user (admin only)
// @Tags issues
// @Produce json
// @Security Bearer
// @Param user_id path int true "User ID"
// @Param limit query int false "Limit" default(20)
// @Param offset query int false "Offset" default(0)
// @Success 200 {object} domain.Response{data=issueListRes}
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues/user/{user_id} [get]
func (h *Handler) GetUserIssues(c *fiber.Ctx) error {
	userIDStr := c.Params("user_id")
	userID, err := strconv.ParseInt(userIDStr, 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Invalid user ID",
			Error:   err.Error(),
		})
	}

	limit, _ := strconv.Atoi(c.Query("limit", "20"))
	offset, _ := strconv.Atoi(c.Query("offset", "0"))
	if limit > 100 {
		limit = 100
	}

	issues, err := h.issueReportingSvc.GetIssuesForUser(c.Context(), userID, limit, offset)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
			Message: "Failed to retrieve user issues",
			Error:   err.Error(),
		})
	}

	totalCount, _ := h.issueReportingSvc.CountIssuesByUser(c.Context(), userID)

	var issueResponses []issueRes
	for _, issue := range issues {
		issueResponses = append(issueResponses, mapIssueToRes(issue))
	}

	return c.JSON(domain.Response{
		Message: "User issues retrieved successfully",
		Data: issueListRes{
			Issues:     issueResponses,
			TotalCount: totalCount,
		},
		Success: true,
	})
}

// GetMyIssues godoc
// @Summary Get my reported issues
// @Description Returns paginated issues reported by the authenticated user
// @Tags issues
// @Produce json
// @Security Bearer
// @Param limit query int false "Limit" default(20)
// @Param offset query int false "Offset" default(0)
// @Success 200 {object} domain.Response{data=issueListRes}
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues/me [get]
func (h *Handler) GetMyIssues(c *fiber.Ctx) error {
	userID := c.Locals("user_id").(int64)

	limit, _ := strconv.Atoi(c.Query("limit", "20"))
	offset, _ := strconv.Atoi(c.Query("offset", "0"))
	if limit > 100 {
		limit = 100
	}

	issues, err := h.issueReportingSvc.GetIssuesForUser(c.Context(), userID, limit, offset)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
			Message: "Failed to retrieve issues",
			Error:   err.Error(),
		})
	}

	totalCount, _ := h.issueReportingSvc.CountIssuesByUser(c.Context(), userID)

	var issueResponses []issueRes
	for _, issue := range issues {
		issueResponses = append(issueResponses, mapIssueToRes(issue))
	}

	return c.JSON(domain.Response{
		Message: "Issues retrieved successfully",
		Data: issueListRes{
			Issues:     issueResponses,
			TotalCount: totalCount,
		},
		Success: true,
	})
}

// GetAllIssues godoc
// @Summary Get all issues
// @Description Returns all reported issues with pagination (admin only)
// @Tags issues
// @Produce json
// @Security Bearer
// @Param limit query int false "Limit" default(20)
// @Param offset query int false "Offset" default(0)
// @Success 200 {object} domain.Response{data=issueListRes}
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues [get]
func (h *Handler) GetAllIssues(c *fiber.Ctx) error {
	limit, _ := strconv.Atoi(c.Query("limit", "20"))
	offset, _ := strconv.Atoi(c.Query("offset", "0"))
	if limit > 100 {
		limit = 100
	}

	issues, err := h.issueReportingSvc.GetAllIssues(c.Context(), limit, offset)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
			Message: "Failed to retrieve issues",
			Error:   err.Error(),
		})
	}

	totalCount, _ := h.issueReportingSvc.CountAllIssues(c.Context())

	var issueResponses []issueRes
	for _, issue := range issues {
		issueResponses = append(issueResponses, mapIssueToRes(issue))
	}

	return c.JSON(domain.Response{
		Message: "Issues retrieved successfully",
		Data: issueListRes{
			Issues:     issueResponses,
			TotalCount: totalCount,
		},
		Success: true,
	})
}

// UpdateIssueStatus godoc
// @Summary Update issue status
// @Description Updates the status of an issue (admin only)
// @Tags issues
// @Accept json
// @Produce json
// @Security Bearer
// @Param id path int true "Issue ID"
// @Param body body updateIssueStatusReq true "Status update payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues/{id}/status [patch]
func (h *Handler) UpdateIssueStatus(c *fiber.Ctx) error {
	idStr := c.Params("id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Invalid issue ID",
			Error:   err.Error(),
		})
	}

	var req updateIssueStatusReq
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Invalid request body",
			Error:   err.Error(),
		})
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		var errMsg string
		for field, msg := range valErrs {
			errMsg += fmt.Sprintf("%s: %s; ", field, msg)
		}
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Validation failed",
			Error:   errMsg,
		})
	}

	if err := h.issueReportingSvc.UpdateIssueStatus(c.Context(), id, req.Status); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
			Message: "Failed to update issue status",
			Error:   err.Error(),
		})
	}

	actorID := c.Locals("user_id").(int64)
	actorRole := string(c.Locals("role").(domain.Role))
	ip := c.IP()
	ua := c.Get("User-Agent")
	meta, _ := json.Marshal(map[string]interface{}{"issue_id": id, "new_status": req.Status})
	go h.activityLogSvc.RecordAction(context.Background(), &actorID, &actorRole, domain.ActionIssueStatusUpdated, domain.ResourceIssue, &id, fmt.Sprintf("Updated issue %d status to %s", id, req.Status), meta, &ip, &ua)

	go func() {
		issue, err := h.issueReportingSvc.GetIssueByID(context.Background(), id)
		if err != nil {
			return
		}
		h.sendInAppNotification(issue.UserID, domain.NOTIFICATION_TYPE_ISSUE_STATUS_UPDATED, "Issue Status Updated", fmt.Sprintf("Your issue \"%s\" has been updated to: %s", issue.Subject, req.Status))
	}()

	return c.JSON(domain.Response{
		Message: "Issue status updated successfully",
		Success: true,
	})
}

// DeleteIssue godoc
// @Summary Delete an issue
// @Description Deletes an issue report (admin only)
// @Tags issues
// @Produce json
// @Security Bearer
// @Param id path int true "Issue ID"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/issues/{id} [delete]
func (h *Handler) DeleteIssue(c *fiber.Ctx) error {
	idStr := c.Params("id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
			Message: "Invalid issue ID",
			Error:   err.Error(),
		})
	}

	if err := h.issueReportingSvc.DeleteIssue(c.Context(), id); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
			Message: "Failed to delete issue",
			Error:   err.Error(),
		})
	}

	actorID := c.Locals("user_id").(int64)
	actorRole := string(c.Locals("role").(domain.Role))
	ip := c.IP()
	ua := c.Get("User-Agent")
	meta, _ := json.Marshal(map[string]interface{}{"issue_id": id})
	go h.activityLogSvc.RecordAction(context.Background(), &actorID, &actorRole, domain.ActionIssueDeleted, domain.ResourceIssue, &id, fmt.Sprintf("Deleted issue ID: %d", id), meta, &ip, &ua)

	return c.JSON(domain.Response{
		Message: "Issue deleted successfully",
		Success: true,
	})
}
