package handlers

import (
	"context"
	"strings"
	"time"

	dutyroster "wucher/internal/domain/duty_roster"
	"wucher/internal/shared/pkg/userctx"
	"wucher/internal/shared/pkg/uuidv7"
	responsedto "wucher/internal/transport/http/dto/response"
)

func loadRosterGroups(ctx context.Context, svc dutyroster.Service, baseID []byte, from, to time.Time, baseName, baseType string) ([]responsedto.DutyRosterBaseGroupResource, error) {
	bt := normalizeRosterBaseType(baseType)
	var (
		headers []dutyroster.HeaderView
		bases   []dutyroster.BaseView
		err     error
	)
	if bt == "base" {
		headers, err = svc.ListHeadersByRangeByBaseType(ctx, baseID, from, to, "base")
		if err != nil {
			return nil, err
		}
		bases, err = svc.ListBasesByType(ctx, baseID, "base")
	} else {
		headers, err = svc.ListHeadersByRange(ctx, baseID, from, to)
		if err != nil {
			return nil, err
		}
		bases, err = svc.ListHEMSBases(ctx, baseID)
	}
	if err != nil {
		return nil, err
	}

	rosterIDs := make([][]byte, 0, len(headers))
	for i := range headers {
		rosterIDs = append(rosterIDs, headers[i].ID)
	}
	crews, err := svc.GetCrewsByRosterIDs(ctx, rosterIDs)
	if err != nil {
		return nil, err
	}

	byRoster := make(map[string][]dutyroster.CrewView)
	for i := range crews {
		k, _ := uuidv7.BytesToString(crews[i].RosterID)
		byRoster[k] = append(byRoster[k], crews[i])
	}
	out := make([]responsedto.DutyRosterResource, 0, len(headers))
	for i := range headers {
		k, _ := uuidv7.BytesToString(headers[i].ID)
		out = append(out, dutyRosterResource(ctx, headers[i], byRoster[k], bt))
	}
	grouped := mergeEmptyRosterGroups(groupRostersByBase(out, bt), bases, bt)

	needle := strings.ToLower(strings.TrimSpace(baseName))
	if needle == "" {
		return grouped, nil
	}
	filtered := make([]responsedto.DutyRosterBaseGroupResource, 0, len(grouped))
	for i := range grouped {
		name := strings.ToLower(strings.TrimSpace(grouped[i].Attributes.Bases.BaseName))
		if strings.Contains(name, needle) {
			filtered = append(filtered, grouped[i])
		}
	}
	return filtered, nil
}

func paginateRosterGroups(groups []responsedto.DutyRosterBaseGroupResource, start, length int) ([]responsedto.DutyRosterBaseGroupResource, int) {
	total := len(groups)
	if start > total {
		start = total
	}
	end := start + length
	if end > total {
		end = total
	}
	return groups[start:end], total
}

func paginateUnifiedRosterGroups(
	hems []responsedto.DutyRosterBaseGroupResource,
	base []responsedto.DutyRosterBaseGroupResource,
	start, length int,
) ([]responsedto.DutyRosterBaseGroupResource, []responsedto.DutyRosterBaseGroupResource, int) {
	total := len(hems) + len(base)
	if start > total {
		start = total
	}
	end := start + length
	if end > total {
		end = total
	}
	if start >= end {
		return []responsedto.DutyRosterBaseGroupResource{}, []responsedto.DutyRosterBaseGroupResource{}, total
	}

	outHEMS := []responsedto.DutyRosterBaseGroupResource{}
	outBase := []responsedto.DutyRosterBaseGroupResource{}

	hemsTotal := len(hems)
	if start < hemsTotal {
		hemsEnd := end
		if hemsEnd > hemsTotal {
			hemsEnd = hemsTotal
		}
		outHEMS = hems[start:hemsEnd]
	}

	baseStart := start - hemsTotal
	if baseStart < 0 {
		baseStart = 0
	}
	if end > hemsTotal && baseStart < len(base) {
		baseEnd := end - hemsTotal
		if baseEnd > len(base) {
			baseEnd = len(base)
		}
		if baseStart < baseEnd {
			outBase = base[baseStart:baseEnd]
		}
	}

	return outHEMS, outBase, total
}

func dutyRosterResource(ctx context.Context, header dutyroster.HeaderView, crews []dutyroster.CrewView, baseType string) responsedto.DutyRosterResource {
	bt := normalizeRosterBaseType(baseType)
	details := responsedto.DutyRosterDetailsResponse{Pilot: []responsedto.DutyRosterPilotCrew{}, Doctor: []responsedto.DutyRosterCrew{}, Rescuer: []responsedto.DutyRosterCrew{}, Helper: []responsedto.DutyRosterCrew{}, OtherPerson: []responsedto.DutyRosterCrew{}}
	for i := range crews {
		baseItem := responsedto.DutyRosterCrew{ID: firstNonEmptyString(idString(crews[i].UserID), idString(crews[i].ID)), Name: strings.TrimSpace(crews[i].Name), Role: strings.TrimSpace(crews[i].Role), MobilePhone: strings.TrimSpace(crews[i].MobilePhone), Email: strings.TrimSpace(crews[i].Email)}
		if roleNeedsShiftDateForDelete(crews[i].RoleCode) {
			baseItem.ShiftDate = &responsedto.DutyRosterShiftDate{DateStart: dateToString(crews[i].DateStart), DateEnd: dateToString(crews[i].DateEnd)}
		}
		effectiveShiftStart := strings.TrimSpace(crews[i].ShiftStart)
		effectiveShiftEnd := strings.TrimSpace(crews[i].ShiftEnd)
		if effectiveShiftStart == "" {
			effectiveShiftStart = strings.TrimSpace(header.ShiftStart)
		}
		if effectiveShiftEnd == "" {
			effectiveShiftEnd = strings.TrimSpace(header.ShiftEnd)
		}
		switch strings.ToLower(strings.TrimSpace(crews[i].RoleCode)) {
		case "pilot":
			pilotItem := responsedto.DutyRosterPilotCrew{ID: baseItem.ID, Name: baseItem.Name, Role: firstNonEmptyString(baseItem.Role, "pilot"), MobilePhone: baseItem.MobilePhone, Email: baseItem.Email, ShiftDate: baseItem.ShiftDate, CrewType: strings.ToLower(strings.TrimSpace(crews[i].CrewType)), FlightInstructor: boolPtrDR(crews[i].FlightInstructor), LineChecker: boolPtrDR(crews[i].LineChecker), Supervisor: boolPtrDR(crews[i].Supervisor), Examiner: boolPtrDR(crews[i].Examiner), CoPilot: boolPtrDR(crews[i].CoPilot)}
			details.Pilot = append(details.Pilot, pilotItem)
		case "doctor":
			if effectiveShiftStart != "" || effectiveShiftEnd != "" {
				baseItem.ShiftTime = &responsedto.DutyRosterShiftTime{ShiftStart: effectiveShiftStart, ShiftEnd: effectiveShiftEnd}
			}
			baseItem.Role = firstNonEmptyString(baseItem.Role, "doctor")
			details.Doctor = append(details.Doctor, baseItem)
		case "rescuer", "air_rescuer":
			if effectiveShiftStart != "" || effectiveShiftEnd != "" {
				baseItem.ShiftTime = &responsedto.DutyRosterShiftTime{ShiftStart: effectiveShiftStart, ShiftEnd: effectiveShiftEnd}
			}
			baseItem.Role = firstNonEmptyString(baseItem.Role, "rescuer")
			details.Rescuer = append(details.Rescuer, baseItem)
		case "helper", "flight_assistant":
			if effectiveShiftStart != "" || effectiveShiftEnd != "" {
				baseItem.ShiftTime = &responsedto.DutyRosterShiftTime{ShiftStart: effectiveShiftStart, ShiftEnd: effectiveShiftEnd}
			}
			baseItem.Role = firstNonEmptyString(baseItem.Role, "helper")
			details.Helper = append(details.Helper, baseItem)
		default:
			baseItem.Role = firstNonEmptyString(baseItem.Role, "other")
			details.OtherPerson = append(details.OtherPerson, baseItem)
		}
	}
	return responsedto.DutyRosterResource{Type: rosterResourceTypeFor(bt), ID: idString(header.ID), Attributes: responsedto.DutyRosterAttributes{Bases: responsedto.DutyRosterBaseInfo{ID: idString(header.BaseID), Type: bt, BaseName: strings.TrimSpace(header.BaseName), BaseAbbreviation: strings.TrimSpace(header.BaseAbbreviation)}, Helicopter: buildRosterHelicopterFromHeader(header), DutyDate: header.DutyDate.UTC().Format("2006-01-02"), ShiftTime: responsedto.DutyRosterShiftTime{ShiftStart: header.ShiftStart, ShiftEnd: header.ShiftEnd}, Detail: details, CreatedBy: uuidStringOrEmptyDR(ctx, header.CreatedBy), CreatedAt: header.CreatedAt.UTC().Format(time.RFC3339), UpdatedAt: header.UpdatedAt.UTC().Format(time.RFC3339)}}
}

func groupRostersByBase(items []responsedto.DutyRosterResource, baseType string) []responsedto.DutyRosterBaseGroupResource {
	bt := normalizeRosterBaseType(baseType)
	groupType := rosterGroupTypeFor(bt)
	out := make([]responsedto.DutyRosterBaseGroupResource, 0)
	indexByBase := map[string]int{}
	for i := range items {
		baseID := strings.TrimSpace(items[i].Attributes.Bases.ID)
		if baseID == "" {
			baseID = "unknown"
		}
		idx, ok := indexByBase[baseID]
		if !ok {
			idx = len(out)
			indexByBase[baseID] = idx
			out = append(out, responsedto.DutyRosterBaseGroupResource{Type: groupType, Attributes: responsedto.DutyRosterBaseGroupAttributes{Bases: items[i].Attributes.Bases, Rosters: []responsedto.DutyRosterGroupRoster{}}})
		}
		out[idx].Attributes.Rosters = append(out[idx].Attributes.Rosters, responsedto.DutyRosterGroupRoster{ID: items[i].ID, FlightID: items[i].Attributes.FlightID, Helicopter: items[i].Attributes.Helicopter, DutyDate: items[i].Attributes.DutyDate, ShiftTime: items[i].Attributes.ShiftTime, Detail: items[i].Attributes.Detail, CreatedBy: items[i].Attributes.CreatedBy, CreatedAt: items[i].Attributes.CreatedAt, UpdatedAt: items[i].Attributes.UpdatedAt})
	}
	return out
}

func mergeEmptyRosterGroups(groups []responsedto.DutyRosterBaseGroupResource, bases []dutyroster.BaseView, baseType string) []responsedto.DutyRosterBaseGroupResource {
	bt := normalizeRosterBaseType(baseType)
	groupType := rosterGroupTypeFor(bt)
	indexByBase := make(map[string]int, len(groups))
	for i := range groups {
		baseID := strings.TrimSpace(groups[i].Attributes.Bases.ID)
		if baseID == "" || baseID == "unknown" {
			continue
		}
		indexByBase[baseID] = i
	}
	for i := range bases {
		baseID := idString(bases[i].ID)
		if baseID == "" {
			continue
		}
		if _, exists := indexByBase[baseID]; exists {
			continue
		}
		groups = append(groups, responsedto.DutyRosterBaseGroupResource{Type: groupType, Attributes: responsedto.DutyRosterBaseGroupAttributes{Bases: responsedto.DutyRosterBaseInfo{ID: baseID, Type: bt, BaseName: strings.TrimSpace(bases[i].BaseName), BaseAbbreviation: strings.TrimSpace(bases[i].BaseAbbreviation)}, Rosters: []responsedto.DutyRosterGroupRoster{}}})
	}
	return groups
}

func dutyRosterDeletedCrewItems(rows []dutyroster.DeletedCrewResult) []map[string]any {
	out := make([]map[string]any, 0, len(rows))
	for i := range rows {
		out = append(out, map[string]any{"id": idString(rows[i].ID), "roster_id": idString(rows[i].RosterID), "user_id": idString(rows[i].UserID), "name": strings.TrimSpace(rows[i].Name), "role_code": strings.TrimSpace(rows[i].RoleCode), "crew_type": strings.TrimSpace(rows[i].CrewType), "date_start": dateToString(rows[i].DateStart), "date_end": dateToString(rows[i].DateEnd)})
	}
	return out
}

func dutyRosterCrewAuditMetadata(rosterID []byte, baseType string, crews []dutyroster.DutyRosterCrew) map[string]any {
	roles := make([]string, 0, len(crews))
	for i := range crews {
		roles = append(roles, roleBucket(crews[i].RoleCode))
	}
	return map[string]any{"roster_id": idString(rosterID), "base_type": strings.ToLower(strings.TrimSpace(baseType)), "roles": roles, "crew_count": len(crews)}
}

func buildRosterHelicopterFromHeader(header dutyroster.HeaderView) responsedto.DutyRosterHelicopterSummary {
	return responsedto.DutyRosterHelicopterSummary{ID: idString(header.HelicopterID), Designation: strings.TrimSpace(header.HelicopterDesignation), Identifier: strings.TrimSpace(header.HelicopterIdentifier), Type: strings.TrimSpace(header.HelicopterType)}
}

func uuidStringOrEmptyDR(ctx context.Context, id []byte) string {
	if len(id) == 0 {
		return ""
	}
	if name := getLastNameFromPresenterGetter(ctx, id); name != "" {
		return name
	}
	return idString(id)
}

func getLastNameFromPresenterGetter(ctx context.Context, id []byte) string {
	if name := userctx.GetDisplayName(ctx, id); name != "" {
		return name
	}
	return ""
}

func firstNonEmptyString(a, b string) string {
	if strings.TrimSpace(a) != "" {
		return strings.TrimSpace(a)
	}
	return strings.TrimSpace(b)
}

func roleBucket(code string) string {
	switch strings.ToLower(strings.TrimSpace(code)) {
	case "pilot":
		return "pilot"
	case "doctor":
		return "doctor"
	case "rescuer", "air_rescuer":
		return "rescuer"
	case "helper", "flight_assistant":
		return "helper"
	default:
		return "other_person"
	}
}

func roleNeedsShiftDateForDelete(roleCode string) bool {
	switch strings.ToLower(strings.TrimSpace(roleCode)) {
	case "other_person", "other":
		return false
	default:
		return true
	}
}

func boolPtrDR(v bool) *bool {
	return &v
}

func normalizeRosterBaseType(baseType string) string {
	if strings.EqualFold(strings.TrimSpace(baseType), "base") {
		return "base"
	}
	return "hems"
}

func rosterResourceTypeFor(baseType string) string {
	if normalizeRosterBaseType(baseType) == "base" {
		return "base_duty_roster"
	}
	return "duty_roster"
}

func rosterGroupTypeFor(baseType string) string {
	if normalizeRosterBaseType(baseType) == "base" {
		return "base_roster_group"
	}
	return "hems_base_roster_group"
}
