package sqlite

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

const providerAccountDeprecatedMissingReason = "missing_from_latest_batch"

func SyncProviderAccountsFromLatestImportBatches(ctx context.Context, store *DB) error {
	if store == nil {
		return fmt.Errorf("store is required")
	}
	batches, err := store.ImportBatches().ListLatestReconcilable(ctx)
	if err != nil {
		return err
	}
	for _, batch := range batches {
		if err := SyncProviderAccountsFromImportBatch(ctx, store, batch.ID); err != nil {
			return err
		}
	}
	return nil
}

func SyncProviderAccountsFromImportBatch(ctx context.Context, store *DB, batchID int64) error {
	if store == nil {
		return fmt.Errorf("store is required")
	}
	if batchID <= 0 {
		return fmt.Errorf("batch_id is required")
	}

	batch, err := store.ImportBatches().GetByID(ctx, batchID)
	if err != nil {
		return fmt.Errorf("get import batch %d: %w", batchID, err)
	}
	switch strings.TrimSpace(batch.BatchStatus) {
	case "succeeded", "partially_succeeded":
	default:
		return nil
	}
	resources, err := store.ManagedResources().GetByBatchID(ctx, batchID)
	if err != nil {
		return fmt.Errorf("get managed resources for batch %d: %w", batchID, err)
	}
	items, err := store.ImportBatchItems().GetByBatchID(ctx, batchID)
	if err != nil {
		return fmt.Errorf("get import batch items for batch %d: %w", batchID, err)
	}

	nowText := time.Now().UTC().Format(time.RFC3339)
	shadowGroupID := ""
	shadowHostID := ""
	for _, resource := range resources {
		if strings.TrimSpace(resource.ResourceType) == "group" {
			shadowGroupID = strings.TrimSpace(resource.HostResourceID)
			break
		}
	}
	hostRow, err := store.Hosts().GetByID(ctx, batch.HostID)
	if err == nil {
		shadowHostID = strings.TrimSpace(hostRow.HostID)
	}
	matchedRoute, routeErr := resolveProviderAccountRouteBinding(ctx, store, shadowHostID, shadowGroupID)
	if routeErr != nil && routeErr != sql.ErrNoRows {
		return routeErr
	}

	accountResources := make([]ManagedResource, 0)
	for _, resource := range resources {
		if strings.TrimSpace(resource.ResourceType) == "account" {
			accountResources = append(accountResources, resource)
		}
	}
	itemByAccountID, unmatchedItems := indexBatchItemsByAccountID(items)
	keepAccountIDs := make([]string, 0, len(accountResources))
	for index, resource := range accountResources {
		hostAccountID := strings.TrimSpace(resource.HostResourceID)
		if hostAccountID == "" {
			continue
		}
		keepAccountIDs = append(keepAccountIDs, hostAccountID)
		match, ok := itemByAccountID[hostAccountID]
		if !ok && index < len(unmatchedItems) {
			match = unmatchedItems[index]
		}
		row := ProviderAccount{
			HostID:          batch.HostID,
			ProviderID:      batch.ProviderID,
			RouteID:         matchedRoute.RouteID,
			ShadowGroupID:   shadowGroupID,
			HostAccountID:   hostAccountID,
			KeyFingerprint:  fallbackString(match.KeyFingerprint, "legacy:"+hostAccountID),
			AccountName:     fallbackString(resource.ResourceName, hostAccountID),
			AccountStatus:   providerAccountStatusFromLegacy(match.AccountStatus),
			LastProbeStatus: strings.TrimSpace(match.ProbeStatus),
			LastProbeAt:     nowText,
		}
		if existing, err := store.ProviderAccounts().GetByHostIDAndAccountID(ctx, batch.HostID, hostAccountID); err == nil {
			if strings.TrimSpace(existing.RouteID) != "" {
				row.RouteID = existing.RouteID
			}
			if strings.TrimSpace(existing.ShadowGroupID) != "" {
				row.ShadowGroupID = existing.ShadowGroupID
			}
			preserveManagedProviderAccountStatus(&row, existing)
		}
		if _, err := store.ProviderAccounts().Upsert(ctx, row); err != nil {
			return fmt.Errorf("upsert provider account %q from batch %d: %w", hostAccountID, batchID, err)
		}
	}
	if err := store.ProviderAccounts().DeprecateMissingForScope(ctx, batch.ProviderID, batch.HostID, keepAccountIDs, providerAccountDeprecatedMissingReason); err != nil {
		return err
	}
	return nil
}

func resolveProviderAccountRouteBinding(ctx context.Context, store *DB, shadowHostID, shadowGroupID string) (LogicalGroupRoute, error) {
	if store == nil {
		return LogicalGroupRoute{}, fmt.Errorf("store is required")
	}
	shadowHostID = strings.TrimSpace(shadowHostID)
	shadowGroupID = strings.TrimSpace(shadowGroupID)
	if shadowHostID == "" || shadowGroupID == "" {
		return LogicalGroupRoute{}, sql.ErrNoRows
	}
	route, err := store.LogicalGroupRoutes().GetByShadowBinding(ctx, shadowHostID, shadowGroupID)
	if err != nil {
		if err == sql.ErrNoRows {
			return LogicalGroupRoute{}, err
		}
		if isAmbiguousProviderAccountRouteBinding(err) {
			return LogicalGroupRoute{}, sql.ErrNoRows
		}
		return LogicalGroupRoute{}, fmt.Errorf("resolve logical route for provider account shadow binding %q/%q: %w", shadowHostID, shadowGroupID, err)
	}
	return route, nil
}

type legacyBatchAccountProjection struct {
	KeyFingerprint string
	AccountStatus  string
	ProbeStatus    string
	AccountID      string
}

func indexBatchItemsByAccountID(items []ImportBatchItem) (map[string]legacyBatchAccountProjection, []legacyBatchAccountProjection) {
	indexed := make(map[string]legacyBatchAccountProjection, len(items))
	unmatched := make([]legacyBatchAccountProjection, 0, len(items))
	for _, item := range items {
		projection := legacyBatchAccountProjection{
			KeyFingerprint: strings.TrimSpace(item.KeyFingerprint),
			AccountStatus:  strings.TrimSpace(item.AccountStatus),
		}
		var payload map[string]any
		if err := json.Unmarshal([]byte(defaultJSON(strings.TrimSpace(item.ProbeSummaryJSON), "{}")), &payload); err == nil {
			if value, ok := payload["probe_status"].(string); ok {
				projection.ProbeStatus = strings.TrimSpace(value)
			}
			if value, ok := payload["account_id"].(string); ok {
				projection.AccountID = strings.TrimSpace(value)
			}
		}
		if projection.AccountID != "" {
			indexed[projection.AccountID] = projection
			continue
		}
		unmatched = append(unmatched, projection)
	}
	return indexed, unmatched
}

func providerAccountStatusFromLegacy(accountStatus string) string {
	switch strings.TrimSpace(accountStatus) {
	case "passed", "warning":
		return ProviderAccountStatusActive
	case ProviderAccountStatusDisabled:
		return ProviderAccountStatusDisabled
	case ProviderAccountStatusDeprecated:
		return ProviderAccountStatusDeprecated
	default:
		return ProviderAccountStatusBroken
	}
}

func fallbackString(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func preserveManagedProviderAccountStatus(row *ProviderAccount, existing ProviderAccount) {
	if row == nil {
		return
	}
	switch strings.TrimSpace(existing.AccountStatus) {
	case ProviderAccountStatusDisabled:
		row.AccountStatus = ProviderAccountStatusDisabled
		row.DisabledReason = strings.TrimSpace(existing.DisabledReason)
	case ProviderAccountStatusDeprecated:
		if strings.TrimSpace(existing.DisabledReason) != providerAccountDeprecatedMissingReason {
			row.AccountStatus = ProviderAccountStatusDeprecated
			row.DisabledReason = strings.TrimSpace(existing.DisabledReason)
		}
	}
}

func isAmbiguousProviderAccountRouteBinding(err error) bool {
	if err == nil {
		return false
	}
	return strings.Contains(err.Error(), "multiple logical group routes match shadow binding")
}
