package service

import (
	"context"
	"strings"

	afterflightinspection "wucher/internal/domain/after_flight_inspection"
	flightdomain "wucher/internal/domain/flight"
	flightdata "wucher/internal/domain/flight_data"
	fmreport "wucher/internal/domain/fm_report"
	sharedconst "wucher/internal/shared/const"
	"wucher/internal/shared/pkg/uuidv7"
)

type aogRecalculator interface {
	RecalculateAOGByMission(ctx context.Context, missionID []byte) error
}

type missionFlightResolver interface {
	FlightIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error)
}

type fmReportLookup interface {
	GetByFlightID(ctx context.Context, flightID []byte) (*fmreport.Report, error)
}

type flightLookup interface {
	GetByID(context.Context, []byte) (*flightdomain.Flight, error)
}

type afterFlightInspectionLookup interface {
	GetByFlightInspectionID(context.Context, []byte) (*afterflightinspection.AfterFlightInspection, error)
}

type FlightDataService struct {
	repo        flightdata.Repository
	aog         aogRecalculator
	mission     missionFlightResolver
	flight      flightLookup
	fmReports   fmReportLookup
	afterFlight afterFlightInspectionLookup
}

func NewFlightDataService(repo flightdata.Repository) *FlightDataService {
	return &FlightDataService{repo: repo}
}

func (s *FlightDataService) WithAOGRecalculator(a aogRecalculator) *FlightDataService {
	s.aog = a
	return s
}

func (s *FlightDataService) WithMissionResolver(resolver missionFlightResolver) *FlightDataService {
	s.mission = resolver
	return s
}

func (s *FlightDataService) WithFlightLookup(lookup flightLookup) *FlightDataService {
	s.flight = lookup
	return s
}

func (s *FlightDataService) WithFMReportLookup(lookup fmReportLookup) *FlightDataService {
	s.fmReports = lookup
	return s
}

func (s *FlightDataService) WithAfterFlightInspectionLookup(lookup afterFlightInspectionLookup) *FlightDataService {
	s.afterFlight = lookup
	return s
}

func (s *FlightDataService) recalcAOG(ctx context.Context, missionID []byte) {
	if s.aog == nil || len(missionID) == 0 {
		return
	}
	_ = s.aog.RecalculateAOGByMission(ctx, missionID)
}

func (s *FlightDataService) Create(ctx context.Context, row *flightdata.FlightData) error {
	if row == nil {
		return flightdata.ErrRequired
	}
	if err := validateFlightDataLocationPair(row.FromICAOID, row.FromHospitalID, true); err != nil {
		return err
	}
	if err := validateFlightDataLocationPair(row.ToICAOID, row.ToHospitalID, false); err != nil {
		return err
	}
	if err := s.canCreateForMission(ctx, row.MissionID); err != nil {
		return err
	}
	row.Status = flightdata.StatusInProgress
	if err := s.repo.Create(ctx, row); err != nil {
		return err
	}
	s.recalcAOG(ctx, row.MissionID)
	if err := s.syncStatus(ctx, row.ID); err != nil {
		return err
	}
	return nil
}

func (s *FlightDataService) CreatePlaceholder(ctx context.Context, row *flightdata.FlightData) error {
	if row == nil {
		return flightdata.ErrRequired
	}
	if err := s.canCreateForMission(ctx, row.MissionID); err != nil {
		return err
	}
	row.Status = flightdata.StatusInProgress
	if err := s.repo.Create(ctx, row); err != nil {
		return err
	}
	return nil
}

func (s *FlightDataService) Update(ctx context.Context, row *flightdata.FlightData) error {
	if row == nil {
		return flightdata.ErrRequired
	}
	if err := validateFlightDataLocationPair(row.FromICAOID, row.FromHospitalID, true); err != nil {
		return err
	}
	if err := validateFlightDataLocationPair(row.ToICAOID, row.ToHospitalID, false); err != nil {
		return err
	}
	if err := s.ensureEditable(ctx, row.MissionID); err != nil {
		return err
	}
	if err := s.repo.Update(ctx, row); err != nil {
		return err
	}
	s.recalcAOG(ctx, row.MissionID)
	if err := s.syncStatus(ctx, row.ID); err != nil {
		return err
	}
	return nil
}

func (s *FlightDataService) Delete(ctx context.Context, id []byte, deletedBy []byte) error {
	var missionID []byte
	if row, err := s.repo.GetByID(ctx, id); err == nil && row != nil {
		missionID = row.MissionID
		if err := s.ensureAfterFlightInspectionNotLocked(ctx, missionID); err != nil {
			return err
		}
		if err := s.ensureEditable(ctx, missionID); err != nil {
			return err
		}
	}
	if err := s.repo.Delete(ctx, id, deletedBy); err != nil {
		return err
	}
	s.recalcAOG(ctx, missionID)
	return nil
}

func (s *FlightDataService) GetByID(ctx context.Context, id []byte) (*flightdata.FlightData, error) {
	return s.repo.GetByID(ctx, id)
}

func (s *FlightDataService) GetByFlightID(ctx context.Context, flightID []byte) (*flightdata.FlightData, error) {
	return s.repo.GetByFlightID(ctx, flightID)
}

func (s *FlightDataService) GetByMissionID(ctx context.Context, missionID []byte) (*flightdata.FlightData, error) {
	return s.repo.GetByMissionID(ctx, missionID)
}

func (s *FlightDataService) ListByMissionID(ctx context.Context, missionID []byte) ([]flightdata.FlightData, error) {
	return s.repo.ListByMissionID(ctx, missionID)
}

func (s *FlightDataService) List(ctx context.Context, date string, missionID, flightDataID []byte, limit, offset int) ([]flightdata.FlightData, int64, error) {
	return s.repo.List(ctx, date, missionID, flightDataID, limit, offset)
}

func (s *FlightDataService) GetSPODetailsByFlightDataID(ctx context.Context, flightDataID []byte) (*flightdata.SPODetails, error) {
	return s.repo.GetSPODetailsByFlightDataID(ctx, flightDataID)
}

func (s *FlightDataService) UpsertSPODetails(ctx context.Context, flightDataID []byte, data *flightdata.SPOUpsertData) error {
	return s.repo.UpsertSPODetails(ctx, flightDataID, data)
}

func (s *FlightDataService) CanCreateForMission(ctx context.Context, missionID []byte) error {
	return s.canCreateForMission(ctx, missionID)
}

func (s *FlightDataService) canCreateForMission(ctx context.Context, missionID []byte) error {
	if err := s.ensureAfterFlightInspectionNotLocked(ctx, missionID); err != nil {
		return err
	}
	flightID, err := s.flightIDByMissionID(ctx, missionID)
	if err != nil {
		return err
	}
	if len(flightID) != 16 || s.fmReports == nil {
		return nil
	}
	report, err := s.fmReports.GetByFlightID(ctx, flightID)
	if err != nil {
		return err
	}
	if report != nil {
		return flightdata.ErrCreateBlocked
	}
	return nil
}

func (s *FlightDataService) ensureEditable(ctx context.Context, missionID []byte) error {
	flightID, err := s.flightIDByMissionID(ctx, missionID)
	if err != nil {
		return err
	}
	if len(flightID) != 16 || s.fmReports == nil {
		return nil
	}
	report, err := s.fmReports.GetByFlightID(ctx, flightID)
	if err != nil {
		return err
	}
	if report != nil && report.CompletedAt != nil {
		return flightdata.ErrLocked
	}
	return nil
}

func (s *FlightDataService) ensureAfterFlightInspectionNotLocked(ctx context.Context, missionID []byte) error {
	flightID, err := s.flightIDByMissionID(ctx, missionID)
	if err != nil {
		return err
	}
	if len(flightID) != 16 || s.flight == nil || s.afterFlight == nil {
		return nil
	}
	flightRow, err := s.flight.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 flightdata.ErrLockedAfterFlightInspection
	}
	return nil
}

func (s *FlightDataService) flightIDByMissionID(ctx context.Context, missionID []byte) ([]byte, error) {
	if len(missionID) != 16 || s.mission == nil {
		return nil, nil
	}
	flightID, err := s.mission.FlightIDByMissionID(ctx, missionID)
	if err != nil {
		return nil, err
	}
	return flightID, nil
}

func (s *FlightDataService) AttachmentReferenceType() string {
	return sharedconst.FlightDataResourceType
}

func (s *FlightDataService) AttachmentReferenceID(row *flightdata.FlightData) string {
	if row == nil {
		return ""
	}
	id, err := uuidv7.BytesToString(row.ID)
	if err != nil {
		return ""
	}
	return id
}

func (s *FlightDataService) syncStatus(ctx context.Context, id []byte) error {
	if len(id) != 16 {
		return nil
	}

	row, err := s.repo.GetByID(ctx, id)
	if err != nil || row == nil {
		return err
	}
	if row.Mission == nil && len(row.MissionID) == 16 {
		if byMissionID, lookupErr := s.repo.GetByMissionID(ctx, row.MissionID); lookupErr == nil && byMissionID != nil {
			row = byMissionID
		}
	}

	details, err := s.loadSPODetails(ctx, row)
	if err != nil {
		return err
	}
	desired := flightdata.DeriveStatus(row, details)
	if strings.EqualFold(strings.TrimSpace(row.Status), desired) {
		return nil
	}
	row.Status = desired
	return s.repo.Update(ctx, row)
}

func (s *FlightDataService) loadSPODetails(ctx context.Context, row *flightdata.FlightData) (*flightdata.SPODetails, error) {
	if row == nil || row.Mission == nil {
		return nil, nil
	}
	if !strings.EqualFold(strings.TrimSpace(row.Mission.Type), sharedconst.MissionTypeSPO) {
		return nil, nil
	}
	details, err := s.repo.GetSPODetailsByFlightDataID(ctx, row.ID)
	if err != nil {
		return nil, err
	}
	return details, nil
}

func validateFlightDataLocationPair(a, b []byte, isOrigin bool) error {
	hasA := len(a) > 0
	hasB := len(b) > 0
	if hasA == hasB {
		if isOrigin {
			return flightdata.ErrOriginLocationReference
		}
		return flightdata.ErrDestinationLocationReference
	}
	return nil
}
