package service

import (
	"context"
	"errors"
	"strconv"
	"strings"
	"time"

	afterflightinspection "wucher/internal/domain/after_flight_inspection"
	basedomain "wucher/internal/domain/base"
	flightdomain "wucher/internal/domain/flight"
	flightdata "wucher/internal/domain/flight_data"
	"wucher/internal/domain/mission"
	sharedconst "wucher/internal/shared/const"
	"wucher/internal/shared/pkg/util"
)

type MissionService struct {
	repo          mission.Repository
	flightSvc     flightdomain.FlightService
	flightDataSvc flightdata.Service
	afterFlight   afterflightinspection.Service
}

func missionCodePrefix(missionType string, t time.Time) string {
	return strings.ToUpper(strings.TrimSpace(missionType)) + "-" + t.Format("06") + "-"
}

func NewMissionService(repo mission.Repository, deps ...any) *MissionService {
	var flightSvc flightdomain.FlightService
	var fdSvc flightdata.Service
	var afterSvc afterflightinspection.Service
	for _, dep := range deps {
		switch v := dep.(type) {
		case flightdomain.FlightService:
			flightSvc = v
		case flightdata.Service:
			fdSvc = v
		case afterflightinspection.Service:
			afterSvc = v
		}
	}
	return &MissionService{repo: repo, flightSvc: flightSvc, flightDataSvc: fdSvc, afterFlight: afterSvc}
}

func (s *MissionService) Create(ctx context.Context, row *mission.Mission) error {
	if row == nil {
		return mission.ErrRequired
	}
	if len(row.FlightID) == 0 {
		return mission.ErrFlightIDRequired
	}
	row.Type = strings.ToUpper(strings.TrimSpace(row.Type))
	category, err := s.repo.FindCategoryByCode(ctx, row.Type)
	if err != nil {
		return err
	}
	if category == nil || len(category.ID) == 0 {
		return mission.ErrTypeInvalid
	}
	row.MissionCategoryID = append([]byte(nil), category.ID...)
	if row.Type == sharedconst.MissionTypeHEMS {
		if len(row.SubtypeID) == 0 {
			return mission.ErrSubtypeRequired
		}
		ok, err := s.repo.SubCategoryBelongsToCategory(ctx, row.SubtypeID, row.MissionCategoryID)
		if err != nil {
			return err
		}
		if !ok {
			return mission.ErrSubtypeInvalid
		}
	} else if len(row.SubtypeID) > 0 {
		return mission.ErrSubtypeForbidden
	}

	startTime, endTime, err := s.resolveMissionTimes(ctx, row.FlightID)
	if err != nil {
		return err
	}
	row.StartTime = startTime
	row.EndTime = endTime
	if err := s.ensureNotAfterFlightLocked(ctx, row.FlightID); err != nil {
		return err
	}

	return s.repo.WithTransaction(ctx, func(txCtx context.Context) error {
		return s.persistMissionCreate(txCtx, row)
	})
}

func (s *MissionService) persistMissionCreate(ctx context.Context, row *mission.Mission) error {
	if strings.TrimSpace(row.Code) == "" {
		prefix := missionCodePrefix(row.Type, time.Now())
		maxSeq, err := s.repo.MaxCodeSeqByPrefix(ctx, prefix)
		if err != nil {
			return err
		}
		row.Code = prefix + strconv.Itoa(maxSeq+1)
	}
	return s.repo.Create(ctx, row)
}

func (s *MissionService) CreateType(ctx context.Context, row *mission.MissionCategory) error {
	if row == nil {
		return mission.ErrRequired
	}
	row.CodeType = strings.ToUpper(strings.TrimSpace(row.CodeType))
	row.TypeName = strings.TrimSpace(row.TypeName)
	if row.CodeType == "" || row.TypeName == "" {
		return mission.ErrTypeInvalid
	}
	return s.repo.CreateCategory(ctx, row)
}

func (s *MissionService) UpdateByID(ctx context.Context, missionID []byte, missionType string, subtypeID []byte, note string, updatedBy []byte) error {
	if len(missionID) == 0 {
		return mission.ErrMissionIDRequired
	}
	missionType = strings.ToUpper(strings.TrimSpace(missionType))
	category, err := s.repo.FindCategoryByCode(ctx, missionType)
	if err != nil {
		return err
	}
	if category == nil || len(category.ID) == 0 {
		return mission.ErrTypeInvalid
	}

	if missionType == sharedconst.MissionTypeHEMS {
		if len(subtypeID) == 0 {
			return mission.ErrSubtypeRequired
		}
		ok, err := s.repo.SubCategoryBelongsToCategory(ctx, subtypeID, category.ID)
		if err != nil {
			return err
		}
		if !ok {
			return mission.ErrSubtypeInvalid
		}
	} else if len(subtypeID) > 0 {
		return mission.ErrSubtypeForbidden
	}

	return s.repo.UpdateByID(ctx, missionID, missionType, category.ID, subtypeID, note, updatedBy)
}

func (s *MissionService) FlightIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error) {
	if len(missionID) != 16 {
		return nil, nil
	}
	row, err := s.repo.GetByID(ctx, missionID)
	if err != nil {
		return nil, err
	}
	if row == nil || len(row.FlightID) != 16 {
		return nil, nil
	}
	return append([]byte(nil), row.FlightID...), nil
}

func (s *MissionService) resolveMissionTimes(ctx context.Context, flightID []byte) (string, string, error) {
	if s.flightSvc == nil {
		return "", "", mission.ErrScheduleMissing
	}
	flightRow, err := s.flightSvc.GetByID(ctx, flightID)
	if err != nil {
		return "", "", err
	}
	if flightRow == nil || len(flightRow.ID) != 16 || flightRow.Date.IsZero() {
		return "", "", mission.ErrScheduleMissing
	}
	baseRow := missionBaseFromFlight(flightRow)
	if baseRow == nil {
		return "", "", mission.ErrBaseMissing
	}
	startTime, endTime, err := util.ResolveShiftWindow(baseRow, flightRow.Date)
	if err != nil {
		return "", "", mapBaseShiftError(err)
	}
	return startTime, endTime, nil
}

func mapBaseShiftError(err error) error {
	switch {
	case errors.Is(err, util.ErrShiftCoordinatesInvalid):
		return mission.ErrCoordinatesInvalid
	case errors.Is(err, util.ErrShiftTwilightMissing):
		return mission.ErrTwilightMissing
	case errors.Is(err, util.ErrShiftScheduleMissing):
		return mission.ErrScheduleMissing
	default:
		return err
	}
}

func (s *MissionService) ensureNotAfterFlightLocked(ctx context.Context, flightID []byte) error {
	if s.flightSvc == nil || s.afterFlight == nil || len(flightID) != 16 {
		return nil
	}
	flightRow, err := s.flightSvc.GetByID(ctx, flightID)
	if err != nil {
		return err
	}
	if flightRow == nil || flightRow.Takeover == nil || flightRow.Takeover.ReserveAc == nil || len(flightRow.Takeover.ReserveAc.InspectionID) != 16 {
		return nil
	}
	afterRow, err := s.afterFlight.GetByFlightInspectionID(ctx, flightRow.Takeover.ReserveAc.InspectionID)
	if err != nil {
		return err
	}
	if afterRow != nil {
		return mission.ErrLockedAfterFlightInspection
	}
	return nil
}

func missionBaseFromFlight(row *flightdomain.Flight) *basedomain.Base {
	if row == nil || row.Takeover == nil {
		return nil
	}
	return row.Takeover.Base
}

func (s *MissionService) DeleteByID(ctx context.Context, missionID []byte, deletedBy []byte) error {
	if len(missionID) == 0 {
		return mission.ErrMissionIDRequired
	}
	return s.repo.DeleteByID(ctx, missionID, deletedBy)
}

func (s *MissionService) DeleteByFlightID(ctx context.Context, flightID []byte, deletedBy []byte) error {
	if len(flightID) == 0 {
		return mission.ErrFlightIDRequired
	}
	return s.repo.DeleteByFlightID(ctx, flightID, deletedBy)
}

func (s *MissionService) AttachFile(ctx context.Context, missionID []byte, fileAttachmentID []byte) error {
	if len(missionID) != 16 {
		return mission.ErrMissionIDRequired
	}
	if len(fileAttachmentID) != 16 {
		return mission.ErrFileAttachmentIDRequired
	}
	return s.repo.AttachFile(ctx, missionID, fileAttachmentID)
}

func (s *MissionService) DetachFile(ctx context.Context, missionID []byte, fileAttachmentID []byte) error {
	if len(missionID) != 16 {
		return mission.ErrMissionIDRequired
	}
	if len(fileAttachmentID) != 16 {
		return mission.ErrFileAttachmentIDRequired
	}
	return s.repo.DetachFile(ctx, missionID, fileAttachmentID)
}

func (s *MissionService) GetByID(ctx context.Context, missionID []byte) (*mission.Mission, error) {
	if len(missionID) == 0 {
		return nil, mission.ErrMissionIDRequired
	}
	row, err := s.repo.GetByID(ctx, missionID)
	if err != nil {
		return nil, err
	}
	if err := s.hydrateFlightDataCompletion(ctx, row); err != nil {
		return nil, err
	}
	return row, nil
}

func (s *MissionService) ListByFlightID(ctx context.Context, flightID []byte) ([]mission.Mission, error) {
	if len(flightID) == 0 {
		return nil, mission.ErrFlightIDRequired
	}
	rows, err := s.repo.ListByFlightID(ctx, flightID)
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (s *MissionService) ListByFlightIDs(ctx context.Context, flightIDs [][]byte) ([]mission.Mission, error) {
	rows, err := s.repo.ListByFlightIDs(ctx, flightIDs)
	if err != nil {
		return nil, err
	}
	return rows, nil
}

func (s *MissionService) GetByFlightID(ctx context.Context, flightID []byte) (*mission.Mission, error) {
	if len(flightID) == 0 {
		return nil, mission.ErrFlightIDRequired
	}
	row, err := s.repo.GetByFlightID(ctx, flightID)
	if err != nil {
		return nil, err
	}
	return row, nil
}

func (s *MissionService) List(ctx context.Context, filter, sort string, flightID []byte, flightDataStatus string, limit, offset int) ([]mission.Mission, int64, error) {
	rows, total, err := s.repo.List(ctx, filter, normalizeMissionSort(sort), flightID, flightDataStatus, limit, offset)
	if err != nil {
		return nil, 0, err
	}
	return rows, total, nil
}

func (s *MissionService) ListDatatable(ctx context.Context, filter mission.ListFilter, limit, offset int) ([]mission.Mission, int64, int64, error) {
	filter.Sort = normalizeMissionSort(filter.Sort)
	rows, total, filtered, err := s.repo.ListDatatable(ctx, filter, limit, offset)
	if err != nil {
		return nil, 0, 0, err
	}
	return rows, total, filtered, nil
}

func (s *MissionService) ListTypes(ctx context.Context) ([]mission.MissionCategory, error) {
	return s.repo.ListCategoriesWithSubCategories(ctx)
}

func (s *MissionService) hydrateFlightDataCompletion(ctx context.Context, row *mission.Mission) error {
	if s.flightDataSvc == nil || row == nil || len(row.ID) == 0 {
		return nil
	}
	fd, err := s.flightDataSvc.GetByMissionID(ctx, row.ID)
	if err != nil {
		return err
	}
	if fd == nil {
		return nil
	}
	var details *flightdata.SPODetails
	if strings.ToUpper(strings.TrimSpace(row.Type)) == sharedconst.MissionTypeSPO {
		details, err = s.flightDataSvc.GetSPODetailsByFlightDataID(ctx, fd.ID)
		if err != nil {
			return err
		}
	}
	row.FlightDataStatus = flightdata.DeriveStatus(fd, details)
	return nil
}

func normalizeMissionSort(sort string) string {
	if normalized := normalizeSortByRules(sort,
		sortField("missions.type", "type"),
		sortField("flights.date", "flight_date"),
		sortField("missions.created_at", "created_at"),
		sortField("missions.updated_at", "updated_at"),
	); normalized != "" {
		return normalized
	}
	return "missions.created_at DESC"
}
