package pso

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"system-altrak/internal/middleware"
	"system-altrak/internal/modules/job"
	"system-altrak/pkg/utils"
	"time"

	"github.com/gofiber/fiber/v2"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type Handler struct {
	service PSOService
	job     job.JobService
}

func NewHandler(s PSOService, j job.JobService) *Handler {
	return &Handler{service: s, job: j}
}

type bulkIDsPayload struct {
	IDs []uint `json:"ids" validate:"required,min=1"`
}

func parseBulkIDsPayload(c *fiber.Ctx) ([]uint, bool) {
	var body bulkIDsPayload
	if err := c.BodyParser(&body); err != nil {
		_ = utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid JSON data structure format")
		return nil, false
	}

	result := utils.ValidateStructDetailed(body)
	if !result.IsValid {
		_ = utils.ValidationErrorResponse(c, "Validation failed", result.Errors)
		return nil, false
	}

	ids := make([]uint, 0, len(body.IDs))
	seen := make(map[uint]struct{}, len(body.IDs))
	for _, id := range body.IDs {
		if id == 0 {
			continue
		}
		if _, ok := seen[id]; ok {
			continue
		}
		seen[id] = struct{}{}
		ids = append(ids, id)
	}

	if len(ids) == 0 {
		_ = utils.ErrorResponse(c, fiber.StatusBadRequest, "No valid IDs provided")
		return nil, false
	}

	return ids, true
}

func mapPSOErrorToResponse(c *fiber.Ctx, err error, fallbackMessage string) error {
	switch {
	case errors.Is(err, gorm.ErrRecordNotFound):
		return utils.NotFoundResponse(c, "Operational Record")
	case errors.Is(err, ErrRecordLocked):
		return utils.ForbiddenResponse(c, "Authorized records cannot be modified")
	case errors.Is(err, ErrInvalidCutoffDate):
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid cutoff format, expected yyyy-mm-dd")
	case errors.Is(err, ErrInvalidExportSearch):
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Search query is too long")
	case errors.Is(err, ErrNoExportData):
		return utils.ErrorResponse(c, fiber.StatusNotFound, "No records found for export")
	case errors.Is(err, ErrPDFEngineUnavailable):
		return utils.ErrorResponse(c, fiber.StatusServiceUnavailable, "PDF engine is not available")
	default:
		return utils.InternalErrorResponse(c, fallbackMessage)
	}
}

func (h *Handler) GetRecord(c *fiber.Ctx) error {
	id, err := utils.ParseParamID(c, "id")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
	}
	branchID, _ := c.Locals("current_branch_id").(uint)
	// We need a GetRecordByID in service too, but serviceImpl just calls repository which already has it.
	// Wait, service implementation doesn't have GetRecordByID yet in the interface.
	// I'll use the repository directly if needed or update service.
	// Using service is better. I'll add GetByID to service.
	record, err := h.service.GetByID(id, branchID)
	if err != nil {
		return utils.NotFoundResponse(c, "Operational Record")
	}
	return utils.SuccessResponse(c, "Record retrieved", record)
}

func (h *Handler) ListOutstanding(c *fiber.Ctx) error {
	branchID, _ := c.Locals("current_branch_id").(uint)
	q := c.Query("search", "")
	startDate := c.Query("start_date", "")
	endDate := c.Query("end_date", "")
	aging := c.Query("aging", "")
	page := c.QueryInt("page", 1)
	limit := c.QueryInt("limit", 100)

	list, total, err := h.service.ListOutstandingRecords(page, limit, q, branchID, startDate, endDate, aging)
	if err != nil {
		return utils.InternalErrorResponse(c, "Failed to aggregate outstanding operational records")
	}

	pagination, err := utils.NewPagination(page, limit, total)
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, err.Error())
	}
	pagination.SetData(list)

	return utils.PaginatedSuccessResponse(c, "Success", list, pagination.GetMeta())
}

func (h *Handler) UpdateRecord(c *fiber.Ctx) error {
	id, err := utils.ParseParamID(c, "id")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
	}

	var data map[string]interface{}
	if err := c.BodyParser(&data); err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid JSON payload")
	}

	data = middleware.SanitizeJSON(data)

	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)
	if err := h.service.UpdateRecord(id, data, branchID, role); err != nil {
		return mapPSOErrorToResponse(c, err, "Failed to update operational record")
	}

	return utils.SuccessResponse(c, "Operational record updated successfully", nil)
}

func (h *Handler) UpdateRemark(c *fiber.Ctx) error {
	id, err := utils.ParseParamID(c, "id")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
	}
	var body struct {
		Remark string `json:"remark" validate:"required,min=1,max=500"`
	}
	if err := c.BodyParser(&body); err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid JSON data structure format")
	}

	middleware.SanitizeStruct(&body)

	// Use new detailed validation
	result := utils.ValidateStructDetailed(body)
	if !result.IsValid {
		return utils.ValidationErrorResponse(c, "Validation failed", result.Errors)
	}

	branchID, _ := c.Locals("current_branch_id").(uint)
	role, _ := c.Locals("role").(string)
	if err := h.service.UpdateRemark(id, body.Remark, branchID, role); err != nil {
		return mapPSOErrorToResponse(c, err, "Failed to update operational remark")
	}
	return utils.SuccessResponse(c, "Operational remark synchronized successfully", nil)
}

func (h *Handler) VerifyItem(c *fiber.Ctx) error {
	id, err := utils.ParseParamID(c, "id")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
	}
	userID, _ := c.Locals("user_id").(uint)
	branchID, _ := c.Locals("current_branch_id").(uint)
	if err := h.service.Verify(id, branchID, userID); err != nil {
		return mapPSOErrorToResponse(c, err, "Failed to verify operational record")
	}
	return utils.SuccessResponse(c, "Operational node verified and authorized", nil)
}

func (h *Handler) BulkVerify(c *fiber.Ctx) error {
	ids, ok := parseBulkIDsPayload(c)
	if !ok {
		return nil
	}

	userID, _ := c.Locals("user_id").(uint)
	branchID, _ := c.Locals("current_branch_id").(uint)
	if err := h.service.BulkVerify(ids, branchID, userID); err != nil {
		return mapPSOErrorToResponse(c, err, "Failed to bulk verify operational records")
	}
	return utils.SuccessResponse(c, fmt.Sprintf("%d operational nodes successfully authorized", len(ids)), nil)
}

func (h *Handler) BulkDelete(c *fiber.Ctx) error {
	ids, ok := parseBulkIDsPayload(c)
	if !ok {
		return nil
	}

	branchID, _ := c.Locals("current_branch_id").(uint)
	deletedCount := 0
	for _, id := range ids {
		if err := h.service.Delete(id, branchID); err != nil {
			return mapPSOErrorToResponse(c, err, "Failed to bulk delete operational records")
		}
		deletedCount++
	}

	return utils.SuccessResponse(c, fmt.Sprintf("%d operational nodes successfully deleted", deletedCount), nil)
}

func (h *Handler) ImportPSO(c *fiber.Ctx) error {
	file, err := c.FormFile("file")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "No document packet received")
	}

	// Strict Validation: Extension
	ext := strings.ToLower(filepath.Ext(file.Filename))
	if ext != ".xlsx" && ext != ".xls" && ext != ".pdf" {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid file format. Only Excel or PDF files (.xlsx, .xls, .pdf) are allowed.")
	}

	// Strict Validation: MIME Type via Magic Bytes
	f, err := file.Open()
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusInternalServerError, "Failed to read file stream")
	}
	buffer := make([]byte, 512)
	n, readErr := f.Read(buffer)
	f.Close()
	if readErr != nil && readErr != io.EOF {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Failed to inspect file signature")
	}
	mimeType := http.DetectContentType(buffer[:n])

	// .xlsx files are detected as application/zip, .xls as application/octet-stream or similar compound obj
	if ext == ".pdf" {
		if !strings.Contains(mimeType, "application/pdf") {
			return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid magic byte signature detected")
		}
	} else if !strings.Contains(mimeType, "application/zip") && !strings.Contains(mimeType, "application/octet-stream") && !strings.Contains(mimeType, "application/vnd.ms-excel") {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid magic byte signature detected")
	}

	tempFile := filepath.Join(os.TempDir(), fmt.Sprintf("temp_pso_%d_%s", time.Now().UnixNano(), file.Filename))
	if err := c.SaveFile(file, tempFile); err != nil {
		return utils.InternalErrorResponse(c, "Failed to cache upload stream: "+err.Error())
	}
	defer os.Remove(tempFile)

	branchID, _ := c.Locals("current_branch_id").(uint)
	userID, _ := c.Locals("user_id").(uint)

	var jobID uint
	if h.job != nil {
		bgJob, _ := h.job.CreateJob("IMPORT_PSO", "Importing PSO from "+file.Filename, 0, branchID, userID)
		if bgJob != nil {
			jobID = bgJob.ID
		}
	}

	summary, err := h.service.ImportExcel(tempFile, branchID, jobID)
	if err != nil {
		// Specific handling for unsupported/corrupted .xls files
		if errors.Is(err, ErrUnsupportedXLS) {
			utils.Log.Warn("Unsupported or corrupted .xls file", zap.Error(err), zap.String("file", file.Filename))
			return utils.ErrorResponse(c, fiber.StatusBadRequest, "File .xls tidak didukung atau rusak, gunakan format .xlsx")
		}
		utils.Log.Error("Import PO Status gagal", zap.Error(err), zap.String("file", file.Filename))
		return mapPSOErrorToResponse(c, err, "Failed to import operational records")
	}

	message := fmt.Sprintf(
		"Import selesai: %d data diproses, %d data baru, %d data diperbarui, %d data dilewati.",
		summary.Processed,
		summary.Inserted,
		summary.Updated,
		summary.Skipped,
	)
	return utils.SuccessResponse(c, message, summary)
}

func (h *Handler) ExportExcel(c *fiber.Ctx) error {
	branchID, _ := c.Locals("current_branch_id").(uint)
	search := c.Query("search", "")
	cutoff := c.Query("cutoff", "")
	buf, err := h.service.ExportExcel(search, cutoff, branchID)
	if err != nil {
		utils.Log.Warn("PO excel export failed", zap.Error(err), zap.Uint("branch_id", branchID))
		return mapPSOErrorToResponse(c, err, "Failed to compile dataset")
	}

	c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
	c.Set("Content-Disposition", `attachment; filename="PO-Status-Intelligence.xlsx"`)
	return c.Send(buf)
}

func (h *Handler) ExportPdf(c *fiber.Ctx) error {
	branchID, _ := c.Locals("current_branch_id").(uint)
	search := c.Query("search", "")
	cutoff := c.Query("cutoff", "")
	buf, err := h.service.ExportPdf(search, cutoff, branchID)
	if err != nil {
		utils.Log.Warn("PO pdf export failed", zap.Error(err), zap.Uint("branch_id", branchID))
		return mapPSOErrorToResponse(c, err, "Failed to compile visual dataset")
	}

	c.Set("Content-Type", "application/pdf")
	disposition := "attachment"
	if strings.EqualFold(c.Query("disposition", ""), "inline") || c.Query("inline", "") == "1" {
		disposition = "inline"
	}
	c.Set("Content-Disposition", fmt.Sprintf(`%s; filename="PO-Status-Intelligence.pdf"`, disposition))
	return c.Send(buf)
}
func (h *Handler) DeleteRecord(c *fiber.Ctx) error {
	id, err := utils.ParseParamID(c, "id")
	if err != nil {
		return utils.ErrorResponse(c, fiber.StatusBadRequest, "Invalid ID parameter")
	}
	branchID, _ := c.Locals("current_branch_id").(uint)
	if err := h.service.Delete(id, branchID); err != nil {
		return mapPSOErrorToResponse(c, err, "Failed to delete operational record")
	}
	return utils.SuccessResponse(c, "Operational record permanently deleted", nil)
}
