package feichibridge

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"sort"
	"strings"
	"sync"
	"time"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/metrics"
)

type Source interface {
	Vehicles(context.Context) ([]Vehicle, error)
	Snapshot(context.Context, string) (Snapshot, error)
	History(context.Context, string, time.Time, time.Time) ([]Record, error)
}

type FrameTarget interface {
	Connect(context.Context) error
	Send(context.Context, []byte) error
	Close() error
	LastACK() time.Time
}

type ServiceConfig struct {
	PollInterval        time.Duration
	DiscoveryInterval   time.Duration
	BackfillInterval    time.Duration
	BackfillLookback    time.Duration
	BackfillWindow      time.Duration
	BackfillSafetyLag   time.Duration
	SourceStaleAfter    time.Duration
	FetchConcurrency    int
	BackfillEnabled     bool
	StaleReissueEnabled bool
}

type Service struct {
	config  ServiceConfig
	source  Source
	target  FrameTarget
	state   *StateStore
	encoder Encoder
	logger  *slog.Logger
	metrics *metrics.Registry

	mu                sync.RWMutex
	vehicles          []Vehicle
	lastSourceSuccess time.Time
	lastError         error
}

func NewService(config ServiceConfig, source Source, target FrameTarget, state *StateStore, logger *slog.Logger, registry *metrics.Registry) (*Service, error) {
	if source == nil || target == nil || state == nil {
		return nil, errors.New("source, target, and state are required")
	}
	if config.PollInterval <= 0 {
		config.PollInterval = 10 * time.Second
	}
	if config.DiscoveryInterval <= 0 {
		config.DiscoveryInterval = 5 * time.Minute
	}
	if config.BackfillInterval <= 0 {
		config.BackfillInterval = time.Hour
	}
	if config.BackfillLookback <= 0 {
		config.BackfillLookback = time.Hour
	}
	if config.BackfillWindow <= 0 {
		config.BackfillWindow = 20 * time.Minute
	}
	if config.BackfillSafetyLag <= 0 {
		config.BackfillSafetyLag = 30 * time.Second
	}
	if config.SourceStaleAfter <= 0 {
		config.SourceStaleAfter = 2 * time.Minute
	}
	if config.FetchConcurrency <= 0 {
		config.FetchConcurrency = 4
	}
	if logger == nil {
		logger = slog.Default()
	}
	return &Service{
		config: config, source: source, target: target, state: state,
		logger: logger, metrics: registry,
	}, nil
}

func (s *Service) Run(ctx context.Context) error {
	if err := s.target.Connect(ctx); err != nil {
		s.setError(err)
		return fmt.Errorf("connect GB/T 32960 target: %w", err)
	}
	if err := s.discover(ctx); err != nil {
		s.setError(err)
		return fmt.Errorf("initial vehicle discovery: %w", err)
	}

	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		s.discoveryLoop(ctx)
	}()
	go func() {
		defer wg.Done()
		s.realtimeLoop(ctx)
	}()
	if s.config.BackfillEnabled {
		wg.Add(1)
		go func() {
			defer wg.Done()
			s.backfillLoop(ctx)
		}()
	}
	<-ctx.Done()
	_ = s.target.Close()
	wg.Wait()
	return nil
}

func (s *Service) Ready(context.Context) error {
	s.mu.RLock()
	lastSourceSuccess := s.lastSourceSuccess
	lastErr := s.lastError
	s.mu.RUnlock()
	maxSourceAge := max(3*s.config.PollInterval, time.Minute)
	if lastSourceSuccess.IsZero() || time.Since(lastSourceSuccess) > maxSourceAge {
		if lastErr != nil {
			return fmt.Errorf("source unavailable: %w", lastErr)
		}
		return errors.New("source has not completed a successful request")
	}
	lastACK := s.target.LastACK()
	if lastACK.IsZero() || time.Since(lastACK) > max(3*s.config.DiscoveryInterval, 10*time.Minute) {
		return errors.New("GB/T 32960 target has no recent ACK")
	}
	return nil
}

func (s *Service) discoveryLoop(ctx context.Context) {
	ticker := time.NewTicker(s.config.DiscoveryInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			if err := s.discover(ctx); err != nil {
				s.recordFailure("discover", err)
			}
		}
	}
}

func (s *Service) realtimeLoop(ctx context.Context) {
	s.pollRealtime(ctx)
	ticker := time.NewTicker(s.config.PollInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			s.pollRealtime(ctx)
		}
	}
}

func (s *Service) backfillLoop(ctx context.Context) {
	s.runBackfill(ctx)
	ticker := time.NewTicker(s.config.BackfillInterval)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			s.runBackfill(ctx)
		}
	}
}

func (s *Service) discover(ctx context.Context) error {
	start := time.Now()
	vehicles, err := s.source.Vehicles(ctx)
	s.observeAPI("vehicles", start, err)
	if err != nil {
		return err
	}
	filtered := make([]Vehicle, 0, len(vehicles))
	for _, vehicle := range vehicles {
		vehicle.VIN = strings.TrimSpace(vehicle.VIN)
		vehicle.VehicleID = strings.TrimSpace(vehicle.VehicleID)
		if len(vehicle.VIN) != 17 || vehicle.VehicleID == "" {
			continue
		}
		if vehicle.RuleTypeName != "" && !strings.Contains(strings.ToUpper(vehicle.RuleTypeName), "32960") {
			continue
		}
		filtered = append(filtered, vehicle)
	}
	sort.Slice(filtered, func(i, j int) bool { return filtered[i].VIN < filtered[j].VIN })
	s.mu.Lock()
	s.vehicles = filtered
	s.lastSourceSuccess = time.Now()
	s.lastError = nil
	s.mu.Unlock()
	if s.metrics != nil {
		s.metrics.SetGauge("vehicle_feichi_bridge_vehicles", nil, float64(len(filtered)))
	}
	s.logger.Info("feichi vehicles discovered", "count", len(filtered))
	return nil
}

type fetchedSnapshot struct {
	vehicle  Vehicle
	record   Record
	at       time.Time
	hash     string
	err      error
	duration time.Duration
}

func (s *Service) pollRealtime(ctx context.Context) {
	vehicles := s.vehicleSnapshot()
	jobs := make(chan Vehicle)
	results := make(chan fetchedSnapshot, len(vehicles))
	var wg sync.WaitGroup
	for worker := 0; worker < min(s.config.FetchConcurrency, len(vehicles)); worker++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for vehicle := range jobs {
				start := time.Now()
				snapshot, err := s.source.Snapshot(ctx, vehicle.VehicleID)
				result := fetchedSnapshot{vehicle: vehicle, duration: time.Since(start), err: err}
				if err == nil {
					result.record = Record(snapshot.DataItems)
					result.at, result.err = recordTime(result.record)
					result.hash = canonicalHash(result.record)
				}
				results <- result
			}
		}()
	}
	for _, vehicle := range vehicles {
		jobs <- vehicle
	}
	close(jobs)
	wg.Wait()
	close(results)

	var snapshots []fetchedSnapshot
	for result := range results {
		s.observeAPIWithDuration("snapshot", result.duration, result.err)
		if result.err != nil {
			s.recordFailure("snapshot", fmt.Errorf("VIN %s: %w", result.vehicle.VIN, result.err))
			continue
		}
		snapshots = append(snapshots, result)
		s.markSourceSuccess()
	}
	sort.Slice(snapshots, func(i, j int) bool {
		if snapshots[i].at.Equal(snapshots[j].at) {
			return snapshots[i].vehicle.VIN < snapshots[j].vehicle.VIN
		}
		return snapshots[i].at.Before(snapshots[j].at)
	})
	for _, snapshot := range snapshots {
		if time.Since(snapshot.at) > s.config.SourceStaleAfter {
			if s.config.StaleReissueEnabled {
				s.reissueStaleSnapshot(ctx, snapshot)
			}
			continue
		}
		current := s.state.Vehicle(snapshot.vehicle.VIN)
		if snapshot.at.Before(current.LastRealtimeTime) ||
			(snapshot.at.Equal(current.LastRealtimeTime) && snapshot.hash == current.LastRealtimeHash) {
			continue
		}
		frame, err := s.encoder.DataFrame(CommandRealtime, snapshot.vehicle.VIN, snapshot.at, snapshot.record)
		if err != nil {
			s.recordFailure("encode", fmt.Errorf("VIN %s: %w", snapshot.vehicle.VIN, err))
			continue
		}
		if err := s.target.Send(ctx, frame); err != nil {
			s.recordFrame(CommandRealtime, "error", snapshot.vehicle.VIN, snapshot.at)
			s.recordFailure("send", fmt.Errorf("VIN %s: %w", snapshot.vehicle.VIN, err))
			continue
		}
		if err := s.state.CommitRealtime(snapshot.vehicle.VIN, snapshot.at, snapshot.hash); err != nil {
			s.recordFailure("state", err)
			continue
		}
		s.recordFrame(CommandRealtime, "acked", snapshot.vehicle.VIN, snapshot.at)
	}
}

func (s *Service) reissueStaleSnapshot(ctx context.Context, snapshot fetchedSnapshot) {
	current := s.state.Vehicle(snapshot.vehicle.VIN)
	if snapshot.at.Before(current.LastSnapshotReissueTime) ||
		(snapshot.at.Equal(current.LastSnapshotReissueTime) && snapshot.hash == current.LastSnapshotReissueHash) {
		return
	}
	frame, err := s.encoder.DataFrame(CommandReissue, snapshot.vehicle.VIN, snapshot.at, snapshot.record)
	if err != nil {
		s.recordFailure("encode_stale_reissue", fmt.Errorf("VIN %s: %w", snapshot.vehicle.VIN, err))
		return
	}
	if err := s.target.Send(ctx, frame); err != nil {
		s.recordFrame(CommandReissue, "error", snapshot.vehicle.VIN, snapshot.at)
		s.recordFailure("send_stale_reissue", fmt.Errorf("VIN %s: %w", snapshot.vehicle.VIN, err))
		return
	}
	if err := s.state.CommitSnapshotReissue(snapshot.vehicle.VIN, snapshot.at, snapshot.hash); err != nil {
		s.recordFailure("state", err)
		return
	}
	s.recordFrame(CommandReissue, "acked", snapshot.vehicle.VIN, snapshot.at)
}

func (s *Service) runBackfill(ctx context.Context) {
	for _, vehicle := range s.vehicleSnapshot() {
		if ctx.Err() != nil {
			return
		}
		if err := s.backfillVehicle(ctx, vehicle); err != nil {
			s.recordFailure("backfill", fmt.Errorf("VIN %s: %w", vehicle.VIN, err))
		}
	}
}

func (s *Service) backfillVehicle(ctx context.Context, vehicle Vehicle) error {
	end := time.Now().Add(-s.config.BackfillSafetyLag)
	cursor := s.state.Vehicle(vehicle.VIN).BackfillCursor
	if cursor.IsZero() {
		cursor = end.Add(-s.config.BackfillLookback)
	}
	for cursor.Before(end) {
		windowEnd := cursor.Add(s.config.BackfillWindow)
		if windowEnd.After(end) {
			windowEnd = end
		}
		start := time.Now()
		records, err := s.source.History(ctx, vehicle.VIN, cursor, windowEnd)
		s.observeAPI("history", start, err)
		if err != nil {
			return err
		}
		sort.Slice(records, func(i, j int) bool {
			left, _ := recordTime(records[i])
			right, _ := recordTime(records[j])
			return left.Before(right)
		})
		committed := cursor
		for _, record := range records {
			at, err := recordTime(record)
			if err != nil || !at.After(cursor) || at.After(windowEnd) {
				continue
			}
			frame, err := s.encoder.DataFrame(CommandReissue, vehicle.VIN, at, record)
			if err != nil {
				return err
			}
			if err := s.target.Send(ctx, frame); err != nil {
				s.recordFrame(CommandReissue, "error", vehicle.VIN, at)
				return err
			}
			if err := s.state.CommitBackfill(vehicle.VIN, at); err != nil {
				return err
			}
			s.recordFrame(CommandReissue, "acked", vehicle.VIN, at)
			committed = at
		}
		if !committed.After(cursor) || committed.Before(windowEnd) {
			if err := s.state.AdvanceBackfillCursor(vehicle.VIN, windowEnd); err != nil {
				return err
			}
		}
		cursor = windowEnd
		s.markSourceSuccess()
	}
	return nil
}

func (s *Service) vehicleSnapshot() []Vehicle {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return append([]Vehicle(nil), s.vehicles...)
}

func (s *Service) markSourceSuccess() {
	s.mu.Lock()
	s.lastSourceSuccess = time.Now()
	s.lastError = nil
	s.mu.Unlock()
	if s.metrics != nil {
		metrics.RecordLastActivity(s.metrics, "vehicle_feichi_bridge_source_last_success_unix_seconds", nil)
	}
}

func (s *Service) setError(err error) {
	s.mu.Lock()
	s.lastError = err
	s.mu.Unlock()
}

func (s *Service) recordFailure(operation string, err error) {
	if err == nil {
		return
	}
	s.setError(err)
	if s.metrics != nil {
		s.metrics.IncCounter("vehicle_feichi_bridge_errors_total", metrics.Labels{"operation": operation})
	}
	s.logger.Error("feichi bridge operation failed", "operation", operation, "error", err)
}

func (s *Service) observeAPI(operation string, start time.Time, err error) {
	s.observeAPIWithDuration(operation, time.Since(start), err)
}

func (s *Service) observeAPIWithDuration(operation string, duration time.Duration, err error) {
	if s.metrics == nil {
		return
	}
	status := "ok"
	if err != nil {
		status = "error"
	}
	s.metrics.IncCounter("vehicle_feichi_bridge_api_requests_total", metrics.Labels{"operation": operation, "status": status})
	s.metrics.ObserveHistogram(
		"vehicle_feichi_bridge_api_request_duration_seconds",
		metrics.Labels{"operation": operation},
		[]float64{0.1, 0.25, 0.5, 1, 2, 5},
		duration.Seconds(),
	)
}

func (s *Service) recordFrame(command byte, status, vin string, sourceTime time.Time) {
	if s.metrics != nil {
		s.metrics.IncCounter("vehicle_feichi_bridge_frames_total", metrics.Labels{
			"command": fmt.Sprintf("0x%02X", command),
			"status":  status,
		})
	}
	if status == "acked" {
		if s.metrics != nil {
			metrics.RecordLastActivity(s.metrics, "vehicle_feichi_bridge_target_last_ack_unix_seconds", nil)
			s.metrics.SetGauge(
				"vehicle_feichi_bridge_vehicle_last_ack_unix_seconds",
				metrics.Labels{"vin": vin, "command": fmt.Sprintf("0x%02X", command)},
				float64(time.Now().Unix()),
			)
		}
		s.logger.Info(
			"GB/T 32960 vehicle frame acknowledged",
			"vin", vin,
			"command", fmt.Sprintf("0x%02X", command),
			"source_time", sourceTime,
		)
	}
}
