package controller

import (
	"math"
	"strconv"
	"strings"
	"time"

	m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
	"gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/dto"
	service "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/services"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/dashboards/validations"
	"gitlab.com/mbugroup/lti-api.git/internal/response"

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

type DashboardController struct {
	DashboardService service.DashboardService
}

func NewDashboardController(dashboardService service.DashboardService) *DashboardController {
	return &DashboardController{
		DashboardService: dashboardService,
	}
}

func (u *DashboardController) GetAll(c *fiber.Ctx) error {
	parseStringListParam := func(param string) ([]string, error) {
		if param == "" {
			return nil, nil
		}
		parts := strings.Split(param, ",")
		result := make([]string, 0, len(parts))
		for _, part := range parts {
			trimmed := strings.TrimSpace(part)
			if trimmed == "" {
				return nil, strconv.ErrSyntax
			}
			result = append(result, trimmed)
		}
		return result, nil
	}

	parseUintListParam := func(param string) ([]uint, error) {
		if param == "" {
			return nil, nil
		}
		parts := strings.Split(param, ",")
		ids := make([]uint, 0, len(parts))
		for _, part := range parts {
			trimmed := strings.TrimSpace(part)
			if trimmed == "" {
				return nil, strconv.ErrSyntax
			}
			parsed, err := strconv.ParseUint(trimmed, 10, 64)
			if err != nil {
				return nil, err
			}
			ids = append(ids, uint(parsed))
		}
		return ids, nil
	}

	lokasiIds, err := parseUintListParam(c.Query("location_ids", ""))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid location_ids")
	}

	flockIds, err := parseUintListParam(c.Query("flock_ids", ""))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid flock_ids")
	}

	kandangIds, err := parseUintListParam(c.Query("kandang_ids", ""))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang_ids")
	}

	include, err := parseStringListParam(strings.ToLower(c.Query("include", "")))
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid include")
	}

	scope, err := m.ResolveLocationScope(c, u.DashboardService.DB())
	if err != nil {
		return err
	}
	if scope.Restrict {
		if len(scope.IDs) == 0 {
			lokasiIds = []uint{}
		} else if len(lokasiIds) > 0 {
			lokasiIds = intersectUint(lokasiIds, scope.IDs)
		} else {
			lokasiIds = scope.IDs
		}
	}

	analysisMode := strings.ToUpper(strings.TrimSpace(c.Query("analysis_mode", validation.AnalysisModeOverview)))
	metric := strings.ToLower(strings.TrimSpace(c.Query("metric", "")))

	query := &validation.Query{
		Page:   c.QueryInt("page", 1),
		Limit:  c.QueryInt("limit", 10),
		Search: strings.TrimSpace(c.Query("search", "")),
		PerformanceOverviewFilter: validation.PerformanceOverviewFilter{
			StartDate:      c.Query("start_date", ""),
			EndDate:        c.Query("end_date", ""),
			AnalysisMode:   analysisMode,
			ComparisonType: strings.ToUpper(strings.TrimSpace(c.Query("comparison_type", ""))),
			Metric:         metric,
			LokasiIds:      lokasiIds,
			FlockIds:       flockIds,
			KandangIds:     kandangIds,
			Include:        include,
		},
	}

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

	if query.AnalysisMode == validation.AnalysisModeComparison && query.ComparisonType == "" {
		return fiber.NewError(fiber.StatusBadRequest, "comparison_type is required for comparison mode")
	}

	location, err := time.LoadLocation("Asia/Jakarta")
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "failed to load timezone configuration")
	}

	startDate, endDate, endExclusive, err := parsePeriodDates(query.StartDate, query.EndDate, location)
	if err != nil {
		return err
	}

	query.PeriodStart = startDate
	query.PeriodEnd = endDate
	query.PeriodEndExclusive = endExclusive

	result, totalResults, err := u.DashboardService.GetAll(c.Context(), query)
	if err != nil {
		return err
	}

	hasFilter := query.StartDate != "" ||
		query.EndDate != "" ||
		len(query.LokasiIds) > 0 ||
		len(query.FlockIds) > 0 ||
		len(query.KandangIds) > 0 ||
		len(query.Include) > 0 ||
		query.ComparisonType != "" ||
		query.Metric != "" ||
		query.AnalysisMode != validation.AnalysisModeOverview

	var filters interface{}
	if hasFilter {
		filters = dto.DashboardFiltersDTO{
			StartDate:      query.StartDate,
			EndDate:        query.EndDate,
			AnalysisMode:   query.AnalysisMode,
			ComparisonType: query.ComparisonType,
			Metric:         query.Metric,
			LokasiIds:      defaultUintSlice(query.LokasiIds),
			FlockIds:       defaultUintSlice(query.FlockIds),
			KandangIds:     defaultUintSlice(query.KandangIds),
			Include:        query.Include,
		}
	}

	return c.Status(fiber.StatusOK).
		JSON(response.SuccessWithMeta{
			Code:    fiber.StatusOK,
			Status:  "success",
			Message: "Get dashboard successfully",
			Meta: response.Meta{
				Page:         query.Page,
				Limit:        query.Limit,
				TotalPages:   int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
				TotalResults: totalResults,
				Filters:      filters,
			},
			Data: result,
		})
}

func defaultUintSlice(values []uint) []uint {
	if values == nil {
		return []uint{}
	}
	return values
}

func intersectUint(a, b []uint) []uint {
	if len(a) == 0 || len(b) == 0 {
		return nil
	}
	set := make(map[uint]struct{}, len(b))
	for _, id := range b {
		set[id] = struct{}{}
	}
	out := make([]uint, 0, len(a))
	for _, id := range a {
		if _, ok := set[id]; ok {
			out = append(out, id)
		}
	}
	return out
}

func parsePeriodDates(startDateRaw, endDateRaw string, location *time.Location) (time.Time, time.Time, time.Time, error) {
	now := time.Now().In(location)
	startDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, location)
	endDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location)

	if startDateRaw != "" {
		parsed, err := time.ParseInLocation("2006-01-02", startDateRaw, location)
		if err != nil {
			return time.Time{}, time.Time{}, time.Time{}, fiber.NewError(fiber.StatusBadRequest, "start_date must follow format YYYY-MM-DD")
		}
		startDate = parsed
	}

	if endDateRaw != "" {
		parsed, err := time.ParseInLocation("2006-01-02", endDateRaw, location)
		if err != nil {
			return time.Time{}, time.Time{}, time.Time{}, fiber.NewError(fiber.StatusBadRequest, "end_date must follow format YYYY-MM-DD")
		}
		endDate = parsed
	}

	if endDate.Before(startDate) {
		return time.Time{}, time.Time{}, time.Time{}, fiber.NewError(fiber.StatusBadRequest, "end_date must be greater than or equal to start_date")
	}

	endExclusive := endDate.AddDate(0, 0, 1)
	return startDate, endDate, endExclusive, nil
}
