package provision

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"sub2api-cn-relay-manager/internal/store/sqlite"
)

type ProviderQuery struct {
	ProviderID string
	PackID     string
	HostID     string
}

type ProviderSnapshot struct {
	Host                   sqlite.Host
	Pack                   sqlite.Pack
	Provider               sqlite.Provider
	Batch                  sqlite.ImportBatch
	ManagedResources       []sqlite.ManagedResource
	AccessClosures         []sqlite.AccessClosureRecord
	ReconcileRuns          []sqlite.ReconcileRun
	ProviderStatus         string
	LatestAccessStatus     string
	LatestReconcileStatus  string
	LatestReconcileSummary map[string]any
}

type ProviderStatusService struct {
	store *sqlite.DB
}

func NewProviderStatusService(store *sqlite.DB) *ProviderStatusService {
	return &ProviderStatusService{store: store}
}

func (s *ProviderStatusService) GetStatus(ctx context.Context, query ProviderQuery) (ProviderSnapshot, error) {
	return s.snapshot(ctx, query)
}

func (s *ProviderStatusService) GetResources(ctx context.Context, query ProviderQuery) (ProviderSnapshot, error) {
	return s.snapshot(ctx, query)
}

func (s *ProviderStatusService) snapshot(ctx context.Context, query ProviderQuery) (ProviderSnapshot, error) {
	if s == nil || s.store == nil {
		return ProviderSnapshot{}, fmt.Errorf("store is required")
	}
	provider, err := s.resolveProvider(ctx, query)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	packRow, err := s.store.Packs().GetByID(ctx, provider.PackID)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	hostRow, batchRow, err := s.resolveHostAndBatch(ctx, provider.ID, query.HostID)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	managedResources, err := s.store.ManagedResources().GetByBatchID(ctx, batchRow.ID)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	accessClosures, err := s.store.AccessClosures().GetByBatchID(ctx, batchRow.ID)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	batches, err := s.store.ImportBatches().ListByProviderIDAndHostID(ctx, provider.ID, hostRow.ID)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	modeStatuses, err := LatestModeAccessStatuses(ctx, s.store, batches)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	latestAccessStatus := AggregateAccessStatus(modeStatuses)
	reconcileRuns, err := s.store.ReconcileRuns().GetByBatchID(ctx, batchRow.ID)
	if err != nil {
		return ProviderSnapshot{}, err
	}
	latestReconcileStatus := "not_run"
	latestReconcileSummary := map[string]any{}
	if len(reconcileRuns) > 0 {
		latestReconcileStatus = firstNonEmpty(reconcileRuns[0].Status, latestReconcileStatus)
		if strings.TrimSpace(reconcileRuns[0].SummaryJSON) != "" {
			if err := json.Unmarshal([]byte(reconcileRuns[0].SummaryJSON), &latestReconcileSummary); err != nil {
				return ProviderSnapshot{}, fmt.Errorf("decode reconcile summary: %w", err)
			}
		}
	}
	providerStatus := deriveProviderStatus(batchRow.BatchStatus, latestAccessStatus, latestReconcileStatus)
	return ProviderSnapshot{
		Host:                   hostRow,
		Pack:                   packRow,
		Provider:               provider,
		Batch:                  batchRow,
		ManagedResources:       managedResources,
		AccessClosures:         accessClosures,
		ReconcileRuns:          reconcileRuns,
		ProviderStatus:         providerStatus,
		LatestAccessStatus:     latestAccessStatus,
		LatestReconcileStatus:  latestReconcileStatus,
		LatestReconcileSummary: latestReconcileSummary,
	}, nil
}

func (s *ProviderStatusService) resolveProvider(ctx context.Context, query ProviderQuery) (sqlite.Provider, error) {
	providerID := strings.TrimSpace(query.ProviderID)
	packID := strings.TrimSpace(query.PackID)
	if providerID == "" {
		return sqlite.Provider{}, fmt.Errorf("provider_id is required")
	}
	if packID != "" {
		packRow, err := s.store.Packs().GetByPackID(ctx, packID)
		if err != nil {
			return sqlite.Provider{}, err
		}
		return s.store.Providers().GetByPackIDAndProviderID(ctx, packRow.ID, providerID)
	}
	providers, err := s.store.Providers().ListByProviderID(ctx, providerID)
	if err != nil {
		return sqlite.Provider{}, err
	}
	if len(providers) == 0 {
		return sqlite.Provider{}, fmt.Errorf("provider %q not found", providerID)
	}
	if len(providers) > 1 {
		return sqlite.Provider{}, fmt.Errorf("provider %q exists in multiple packs; pack_id is required", providerID)
	}
	return providers[0], nil
}

func (s *ProviderStatusService) resolveHostAndBatch(ctx context.Context, providerID int64, hostQuery string) (sqlite.Host, sqlite.ImportBatch, error) {
	if strings.TrimSpace(hostQuery) != "" {
		hostRow, err := s.store.Hosts().GetByHostID(ctx, hostQuery)
		if err != nil {
			return sqlite.Host{}, sqlite.ImportBatch{}, err
		}
		batchRow, err := s.store.ImportBatches().GetLatestByProviderIDAndHostID(ctx, providerID, hostRow.ID)
		if err != nil {
			return sqlite.Host{}, sqlite.ImportBatch{}, err
		}
		return hostRow, batchRow, nil
	}

	batches, err := s.store.ImportBatches().ListByProviderID(ctx, providerID)
	if err != nil {
		return sqlite.Host{}, sqlite.ImportBatch{}, err
	}
	if len(batches) == 0 {
		return sqlite.Host{}, sqlite.ImportBatch{}, fmt.Errorf("latest import batch not found for provider")
	}
	hostID := batches[0].HostID
	for _, batch := range batches[1:] {
		if batch.HostID != hostID {
			return sqlite.Host{}, sqlite.ImportBatch{}, fmt.Errorf("provider exists on multiple hosts; host_id is required")
		}
	}
	hostRow, err := s.store.Hosts().GetByID(ctx, hostID)
	if err != nil {
		return sqlite.Host{}, sqlite.ImportBatch{}, err
	}
	return hostRow, batches[0], nil
}

func deriveProviderStatus(batchStatus, accessStatus, reconcileStatus string) string {
	batchStatus = strings.TrimSpace(batchStatus)
	reconcileStatus = strings.TrimSpace(reconcileStatus)
	accessStatus = strings.TrimSpace(accessStatus)
	switch batchStatus {
	case BatchStatusFailed, BatchStatusRolledBack:
		return ProviderStatusFailed
	}
	if (batchStatus == BatchStatusSucceeded || batchStatus == BatchStatusPartial) && accessStatus != "" && accessStatus != AccessStatusBroken && reconcileStatus == ProviderStatusActive {
		return ProviderStatusActive
	}
	if batchStatus == BatchStatusSucceeded && accessStatus != "" && accessStatus != AccessStatusBroken {
		return ProviderStatusActive
	}
	if reconcileStatus != "" && reconcileStatus != "not_run" {
		return reconcileStatus
	}
	switch batchStatus {
	case BatchStatusSucceeded:
		return ProviderStatusActive
	case "running":
		return "running"
	default:
		return firstNonEmpty(batchStatus, "unknown")
	}
}
