package service

import (
	"context"
	"testing"
	"time"

	fleethistory "wucher/internal/domain/fleet_history"
	fleetstatus "wucher/internal/domain/fleet_status"
	"wucher/internal/domain/helicopter"
	helicopterusage "wucher/internal/domain/helicopter_usage"
)

type stubFleetRepo struct {
	latest      map[string]*fleetstatus.FleetStatus
	missionHeli []byte
	created     *fleetstatus.FleetStatus
	updated     *fleetstatus.FleetStatus
	deletedID   []byte
	servicedID  []byte
}

func (s *stubFleetRepo) Create(_ context.Context, row *fleetstatus.FleetStatus) error {
	s.created = row
	return nil
}
func (s *stubFleetRepo) Update(_ context.Context, row *fleetstatus.FleetStatus) error {
	s.updated = row
	return nil
}
func (s *stubFleetRepo) Delete(_ context.Context, id, _ []byte) error { s.deletedID = id; return nil }
func (s *stubFleetRepo) GetByID(_ context.Context, _ []byte) (*fleetstatus.FleetStatus, error) {
	return s.created, nil
}
func (s *stubFleetRepo) List(_ context.Context, _, _ string, _, _ int) ([]fleetstatus.FleetStatus, int64, error) {
	return nil, 0, nil
}
func (s *stubFleetRepo) LatestByHelicopterIDs(_ context.Context, _ [][]byte) (map[string]*fleetstatus.FleetStatus, error) {
	return s.latest, nil
}
func (s *stubFleetRepo) MarkServiced(_ context.Context, id []byte, _ time.Time, _ []byte) error {
	s.servicedID = id
	return nil
}
func (s *stubFleetRepo) ListServiceHistoryByHelicopterID(_ context.Context, _ []byte, _, _ int) ([]fleetstatus.FleetStatusServiceLog, int64, error) {
	return nil, 0, nil
}
func (s *stubFleetRepo) HelicopterIDByMissionID(_ context.Context, _ []byte) ([]byte, error) {
	return s.missionHeli, nil
}
func (s *stubFleetRepo) HelicopterIDsWithFleetStatus(_ context.Context) ([][]byte, error) {
	out := make([][]byte, 0, len(s.latest))
	for k := range s.latest {
		out = append(out, []byte(k))
	}
	return out, nil
}

type stubFleetHelis struct {
	heli    *helicopter.Helicopter
	updated *helicopter.Helicopter
}

func (s *stubFleetHelis) GetByID(_ context.Context, _ []byte) (*helicopter.Helicopter, error) {
	return s.heli, nil
}
func (s *stubFleetHelis) Update(_ context.Context, h *helicopter.Helicopter) error {
	s.updated = h
	return nil
}

type stubFleetUsage struct {
	usage *helicopterusage.HelicopterUsage
}

func (s *stubFleetUsage) GetByHelicopterID(_ context.Context, _ []byte) (*helicopterusage.HelicopterUsage, error) {
	return s.usage, nil
}

func (s *stubFleetUsage) ReviseManualTotals(_ context.Context, _ []byte, _ helicopterusage.ManualSummaryInput, _ []byte) error {
	return nil
}

func fleetUsage(airframeHours float64) *stubFleetUsage {
	return &stubFleetUsage{usage: &helicopterusage.HelicopterUsage{TotalAirframeHours: airframeHours}}
}

type stubHistoryRepo struct{ recorded int }

func (s *stubHistoryRepo) Create(_ context.Context, _ *fleethistory.FleetHistory) error {
	s.recorded++
	return nil
}
func (s *stubHistoryRepo) ListByHelicopter(_ context.Context, _ []byte, _, _ int) ([]fleethistory.FleetHistory, int64, error) {
	return nil, 0, nil
}
func (s *stubHistoryRepo) HelicopterIDByInspection(_ context.Context, _ []byte) ([]byte, error) {
	return nil, nil
}

func newHistory() *FleetHistoryService { return NewFleetHistoryService(&stubHistoryRepo{}) }

func heliID() []byte { return []byte("0123456789abcdef") }

func fleetWith(schedDue string) *fleetstatus.FleetStatus {
	return &fleetstatus.FleetStatus{
		ID:           []byte("fedcba9876543210"),
		HelicopterID: heliID(),
		MaintenanceSchedules: []fleetstatus.MaintenanceSchedule{
			{InspectionType: fleetstatus.InspectionTypeAF, Due: schedDue},
		},
	}
}

func TestSyncAutomaticAOG_SetWhenOverdue(t *testing.T) {
	row := fleetWith("100")
	repo := &stubFleetRepo{
		latest: map[string]*fleetstatus.FleetStatus{string(heliID()): row},
	}
	helis := &stubFleetHelis{heli: &helicopter.Helicopter{ID: heliID(), NR1: true, NR2: true}}
	svc := NewFleetStatusService(repo, nil, helis, nil).WithHistory(newHistory()).WithUsage(fleetUsage(150))

	if err := svc.RecalculateAOGByHelicopter(context.Background(), heliID()); err != nil {
		t.Fatalf("err: %v", err)
	}
	if helis.updated == nil || !helis.updated.AirOnGround {
		t.Fatal("expected helicopter set AOG (150h > 100h due)")
	}
	if helis.updated.AOGSource != helicopter.AOGSourceAuto {
		t.Fatalf("expected auto AOG source, got %q", helis.updated.AOGSource)
	}
}

func TestSyncAutomaticAOG_NotOverdueNotGrounded(t *testing.T) {
	row := fleetWith("100")
	repo := &stubFleetRepo{
		latest: map[string]*fleetstatus.FleetStatus{string(heliID()): row},
	}
	helis := &stubFleetHelis{heli: &helicopter.Helicopter{ID: heliID(), NR1: true, NR2: true}}
	svc := NewFleetStatusService(repo, nil, helis, nil).WithUsage(fleetUsage(50))

	if err := svc.RecalculateAOGByHelicopter(context.Background(), heliID()); err != nil {
		t.Fatalf("err: %v", err)
	}
	if helis.updated != nil {
		t.Fatal("not overdue and not previously auto-AOG: should not touch helicopter")
	}
}

func TestSyncAutomaticAOG_NoUsageDataNotGrounded(t *testing.T) {
	row := fleetWith("100")
	repo := &stubFleetRepo{
		latest: map[string]*fleetstatus.FleetStatus{string(heliID()): row},
	}
	helis := &stubFleetHelis{heli: &helicopter.Helicopter{ID: heliID(), NR1: true, NR2: true}}
	svc := NewFleetStatusService(repo, nil, helis, nil).WithUsage(&stubFleetUsage{usage: nil})

	if err := svc.RecalculateAOGByHelicopter(context.Background(), heliID()); err != nil {
		t.Fatalf("err: %v", err)
	}
	if helis.updated != nil {
		t.Fatal("fresh aircraft with no usage data must not be auto-AOG on an hours due")
	}
}

func TestSyncAutomaticAOG_ClearWhenNoLongerOverdue(t *testing.T) {
	row := fleetWith("100")
	repo := &stubFleetRepo{
		latest: map[string]*fleetstatus.FleetStatus{string(heliID()): row},
	}
	helis := &stubFleetHelis{heli: &helicopter.Helicopter{
		ID: heliID(), NR1: true, NR2: true,
		AirOnGround: true, AOGSource: helicopter.AOGSourceAuto, AOGReason: "[auto_next_due] A/F due hours reached",
	}}
	svc := NewFleetStatusService(repo, nil, helis, nil).WithHistory(newHistory()).WithUsage(fleetUsage(50))

	if err := svc.RecalculateAOGByHelicopter(context.Background(), heliID()); err != nil {
		t.Fatalf("err: %v", err)
	}
	if helis.updated == nil || helis.updated.AirOnGround {
		t.Fatal("auto-AOG should be cleared once no longer overdue")
	}
}

func TestFleetStatusCreateUpdateGet(t *testing.T) {
	repo := &stubFleetRepo{}
	svc := NewFleetStatusService(repo, nil, nil, nil) // attachments nil -> simple path
	ctx := context.Background()
	row := &fleetstatus.FleetStatus{HelicopterID: heliID()}
	if err := svc.Create(ctx, row); err != nil || repo.created != row {
		t.Fatalf("Create: %v", err)
	}
	if err := svc.Update(ctx, row); err != nil || repo.updated != row {
		t.Fatalf("Update: %v", err)
	}
	if _, err := svc.GetByID(ctx, heliID()); err != nil {
		t.Fatalf("GetByID: %v", err)
	}
}

func TestRecalculateAOGByMission(t *testing.T) {
	row := fleetWith("100")
	repo := &stubFleetRepo{
		latest:      map[string]*fleetstatus.FleetStatus{string(heliID()): row},
		missionHeli: heliID(),
	}
	helis := &stubFleetHelis{heli: &helicopter.Helicopter{ID: heliID(), NR1: true, NR2: true}}
	svc := NewFleetStatusService(repo, nil, helis, nil).WithUsage(fleetUsage(150))

	if err := svc.RecalculateAOGByMission(context.Background(), []byte("mission0123456789")); err != nil {
		t.Fatalf("err: %v", err)
	}
	if helis.updated == nil || !helis.updated.AirOnGround {
		t.Fatal("mission recalc should set AOG")
	}

	// empty mission id -> no-op
	if err := svc.RecalculateAOGByMission(context.Background(), nil); err != nil {
		t.Fatalf("nil mission err: %v", err)
	}
	// no fleet status for helicopter -> no-op
	if err := NewFleetStatusService(&stubFleetRepo{}, nil, helis, nil).RecalculateAOGByHelicopter(context.Background(), heliID()); err != nil {
		t.Fatalf("no-fleet-status err: %v", err)
	}
}

func TestRecalculateAllAOG(t *testing.T) {
	row := fleetWith("100")
	repo := &stubFleetRepo{
		latest: map[string]*fleetstatus.FleetStatus{string(heliID()): row},
	}
	helis := &stubFleetHelis{heli: &helicopter.Helicopter{ID: heliID(), NR1: true, NR2: true}}
	svc := NewFleetStatusService(repo, nil, helis, nil).WithUsage(fleetUsage(150))

	n, err := svc.RecalculateAllAOG(context.Background())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if n != 1 {
		t.Fatalf("expected 1 helicopter recomputed, got %d", n)
	}
	if helis.updated == nil || !helis.updated.AirOnGround {
		t.Fatal("overdue helicopter should have been set AOG by the sweep")
	}
}

func TestGetSummaryByHelicopterID_EngineGating(t *testing.T) {
	usage := &stubFleetUsage{usage: &helicopterusage.HelicopterUsage{
		TotalAirframeHours: 6620,
		TotalEngine1Hours:  8011,
		TotalEngine2Hours:  5981,
	}}
	helis := &stubFleetHelis{heli: &helicopter.Helicopter{ID: heliID(), NR1: true, NR2: false}}
	svc := NewFleetStatusService(&stubFleetRepo{}, nil, helis, nil).WithUsage(usage)

	summary, err := svc.GetSummaryByHelicopterID(context.Background(), heliID())
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	assertFloatPtr(t, "airframe", summary.Airframe.Hours, ptr(6620.0))
	assertFloatPtr(t, "engine1", summary.Engine1.Hours, ptr(8011.0))
	if summary.Engine2.Hours != nil {
		t.Fatal("engine 2 absent (NR2 false) -> hours must be nil")
	}
}

func TestFleetStatusSimpleDelegators(t *testing.T) {
	repo := &stubFleetRepo{}
	svc := NewFleetStatusService(repo, nil, nil, nil).WithHistory(nil)
	ctx := context.Background()
	if err := svc.Delete(ctx, []byte("id"), nil); err != nil || string(repo.deletedID) != "id" {
		t.Fatalf("Delete: %v", err)
	}
	if _, _, err := svc.List(ctx, "", "", 0, 0); err != nil {
		t.Fatalf("List: %v", err)
	}
	if _, err := svc.LatestByHelicopterIDs(ctx, [][]byte{heliID()}); err != nil {
		t.Fatalf("LatestByHelicopterIDs: %v", err)
	}
	if _, _, err := svc.ListServiceHistoryByHelicopterID(ctx, heliID(), 0, 0); err != nil {
		t.Fatalf("ListServiceHistory: %v", err)
	}
	if err := svc.MarkServiced(ctx, []byte("ms"), time.Time{}, nil); err != nil || string(repo.servicedID) != "ms" {
		t.Fatalf("MarkServiced: %v", err)
	}
}
