package validation

import (
	"mime/multipart"
	"strconv"
	"strings"
	"time"

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

type Create struct {
	Date                  string `form:"date" validate:"required"`
	ProjectFlockKandangId uint   `form:"project_flock_kandang_id" validate:"required,number,min=1"`
	Week                  int    `form:"week" validate:"omitempty,min=1"`
}

type Update struct {
	Date                  *string `json:"date,omitempty" form:"date" validate:"omitempty"`
	ProjectFlockKandangId *uint   `json:"project_flock_kandang_id,omitempty" form:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
	Week                  *int    `json:"week,omitempty" form:"week" validate:"omitempty,min=1"`
}

type Query struct {
	Page                  int    `query:"page" validate:"omitempty,number,min=1,gt=0"`
	Limit                 int    `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
	ProjectFlockKandangId uint   `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
	Week                  int    `query:"week" validate:"omitempty,min=1"`
	StartDate             string `query:"start_date" validate:"omitempty"`
	EndDate               string `query:"end_date" validate:"omitempty"`
	WithChart             bool   `query:"with_chart"`
}

type UploadExcelRequest struct {
	Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
}

type Approve struct {
	Action        string  `json:"action" validate:"required_strict"`
	ApprovableIds []uint  `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
	Notes         *string `json:"notes,omitempty" validate:"omitempty,max=500"`
}

const maxUniformityUploadBytes = 5 * 1024 * 1024

func ParseIDParam(c *fiber.Ctx, name string) (uint, error) {
	raw := strings.TrimSpace(c.Params(name))
	if raw == "" {
		return 0, fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
	}
	id, err := strconv.Atoi(raw)
	if err != nil || id <= 0 {
		return 0, fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
	}
	return uint(id), nil
}

func ParseQuery(c *fiber.Ctx) (*Query, error) {
	query := &Query{
		Page:                  c.QueryInt("page", 1),
		Limit:                 c.QueryInt("limit", 10),
		ProjectFlockKandangId: uint(c.QueryInt("project_flock_kandang_id", 0)),
		Week:                  c.QueryInt("week", 0),
		StartDate:             strings.TrimSpace(c.Query("start_date")),
		EndDate:               strings.TrimSpace(c.Query("end_date")),
		WithChart:             c.QueryBool("with_chart", false),
	}

	if query.Page < 1 || query.Limit < 1 {
		return nil, fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
	}

	if _, _, err := ParseDateRange(query.StartDate, query.EndDate); err != nil {
		return nil, err
	}

	return query, nil
}

func ParseDateRange(startDate, endDate string) (*time.Time, *time.Time, error) {
	var startDateValue *time.Time
	var endDateValue *time.Time

	if startDate != "" {
		parsed, err := time.Parse("2006-01-02", startDate)
		if err != nil {
			return nil, nil, fiber.NewError(fiber.StatusBadRequest, "start_date must be in YYYY-MM-DD format")
		}
		startDateValue = &parsed
	}
	if endDate != "" {
		parsed, err := time.Parse("2006-01-02", endDate)
		if err != nil {
			return nil, nil, fiber.NewError(fiber.StatusBadRequest, "end_date must be in YYYY-MM-DD format")
		}
		endDateValue = &parsed
	}
	if startDateValue != nil && endDateValue != nil {
		if endDateValue.Before(*startDateValue) {
			return nil, nil, fiber.NewError(fiber.StatusBadRequest, "end_date must be greater than or equal to start_date")
		}
	}

	return startDateValue, endDateValue, nil
}

func ParseCreate(c *fiber.Ctx) (*Create, *multipart.FileHeader, error) {
	date := strings.TrimSpace(c.FormValue("date"))
	if date == "" {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "date is required")
	}

	projectFlockKandangIDStr := strings.TrimSpace(c.FormValue("project_flock_kandang_id"))
	if projectFlockKandangIDStr == "" {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
	}

	projectFlockKandangID, err := strconv.Atoi(projectFlockKandangIDStr)
	if err != nil || projectFlockKandangID <= 0 {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required")
	}

	week := 0
	weekStr := strings.TrimSpace(c.FormValue("week"))
	if weekStr != "" {
		parsedWeek, err := strconv.Atoi(weekStr)
		if err != nil || parsedWeek <= 0 {
			return nil, nil, fiber.NewError(fiber.StatusBadRequest, "week is invalid")
		}
		week = parsedWeek
	}

	file, err := c.FormFile("document")
	if err != nil {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "document is required")
	}
	if err := validateUniformityFileSize(file); err != nil {
		return nil, nil, err
	}

	return &Create{
		Date:                  date,
		ProjectFlockKandangId: uint(projectFlockKandangID),
		Week:                  week,
	}, file, nil
}

func ParseUpdate(c *fiber.Ctx) (*Update, *multipart.FileHeader, error) {
	contentType := strings.ToLower(c.Get("Content-Type"))
	if strings.Contains(contentType, "multipart/form-data") {
		req := &Update{}

		date := strings.TrimSpace(c.FormValue("date"))
		if date != "" {
			req.Date = &date
		}

		projectFlockKandangIDStr := strings.TrimSpace(c.FormValue("project_flock_kandang_id"))
		if projectFlockKandangIDStr != "" {
			projectFlockKandangID, err := strconv.Atoi(projectFlockKandangIDStr)
			if err != nil || projectFlockKandangID <= 0 {
				return nil, nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is invalid")
			}
			idCopy := uint(projectFlockKandangID)
			req.ProjectFlockKandangId = &idCopy
		}

		weekStr := strings.TrimSpace(c.FormValue("week"))
		if weekStr != "" {
			week, err := strconv.Atoi(weekStr)
			if err != nil || week <= 0 {
				return nil, nil, fiber.NewError(fiber.StatusBadRequest, "week is invalid")
			}
			req.Week = &week
		}

		file, err := c.FormFile("document")
		if err != nil {
			file = nil
		} else if err := validateUniformityFileSize(file); err != nil {
			return nil, nil, err
		}

		return req, file, nil
	}

	req := new(Update)
	if err := c.BodyParser(req); err != nil {
		return nil, nil, fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}
	return req, nil, nil
}

func ParseUploadFiles(c *fiber.Ctx) ([]*multipart.FileHeader, error) {
	file, err := c.FormFile("document")
	if err != nil || file == nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "document is required")
	}
	if err := validateUniformityFileSize(file); err != nil {
		return nil, err
	}

	return []*multipart.FileHeader{file}, nil
}

func ParseApprove(c *fiber.Ctx) (*Approve, error) {
	req := new(Approve)
	if err := c.BodyParser(req); err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}
	return req, nil
}

func validateUniformityFileSize(file *multipart.FileHeader) error {
	if file != nil && file.Size > maxUniformityUploadBytes {
		return fiber.NewError(fiber.StatusRequestEntityTooLarge, "Document size must be <= 5MB")
	}
	return nil
}
