package controlplane

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
)

// applyAllSourceRevisionAdvanceCurrentSnapshot freezes the complete installed
// state of one normalized plan before any source lookup.
type applyAllSourceRevisionAdvanceCurrentSnapshot struct {
	entries []applyAllSourceRevisionAdvanceCurrentSnapshotEntry
}

type applyAllSourceRevisionAdvanceCurrentSnapshotEntry struct {
	advance normalizedApplyAllSourceRevisionAdvance
	row     installedPackRow
	pending *PendingApplyAllSourceRevisionAdvance
}

func (s applyAllSourceRevisionAdvanceCurrentSnapshot) currentRevisions() map[string]string {
	revisions := make(map[string]string, len(s.entries))
	for _, entry := range s.entries {
		revisions[strings.ToLower(entry.advance.URI)] = strings.TrimSpace(entry.row.SourceRevision.String)
	}
	return revisions
}

// replayRevisions returns the exact source tuple needed to reconstruct the
// transition base for an execution attempt. A failed/installing InstalledPack
// row already names ToRevision, but its still-active immutable generation
// remains at FromRevision. Retrying must therefore reconstruct FromRevision so
// removal-only lifecycle proof is not collapsed into a target-to-target no-op.
func (s applyAllSourceRevisionAdvanceCurrentSnapshot) replayRevisions() map[string]string {
	revisions := s.currentRevisions()
	for _, entry := range s.entries {
		if entry.pending == nil || entry.pending.Kind != ApplyAllSourceRevisionAdvancePendingRetry {
			continue
		}
		revisions[strings.ToLower(entry.advance.URI)] = entry.advance.FromRevision
	}
	return revisions
}

func (s applyAllSourceRevisionAdvanceCurrentSnapshot) entry(
	name string,
) (applyAllSourceRevisionAdvanceCurrentSnapshotEntry, bool) {
	name = strings.ToLower(strings.TrimSpace(name))
	for _, candidate := range s.entries {
		if strings.ToLower(candidate.advance.Name) != name {
			continue
		}
		if candidate.pending != nil {
			pending := *candidate.pending
			candidate.pending = &pending
		}
		return candidate, true
	}
	return applyAllSourceRevisionAdvanceCurrentSnapshotEntry{}, false
}

func readApplyAllSourceRevisionAdvanceCurrentSnapshot(
	ctx context.Context,
	db *sql.DB,
	plan []normalizedApplyAllSourceRevisionAdvance,
) (applyAllSourceRevisionAdvanceCurrentSnapshot, error) {
	snapshot := applyAllSourceRevisionAdvanceCurrentSnapshot{
		entries: make([]applyAllSourceRevisionAdvanceCurrentSnapshotEntry, 0, len(plan)),
	}
	if len(plan) == 0 {
		return snapshot, nil
	}
	if db == nil {
		return applyAllSourceRevisionAdvanceCurrentSnapshot{}, applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanRead, -1, "", fmt.Errorf("database connection is unavailable"),
		)
	}
	for _, advance := range plan {
		row, ok, err := getInstalledPackRowByNameForSourceRevisionAdvance(ctx, db, advance.Name)
		if err != nil {
			return applyAllSourceRevisionAdvanceCurrentSnapshot{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanRead, advance.index, advance.Name, err,
			)
		}
		if !ok {
			return applyAllSourceRevisionAdvanceCurrentSnapshot{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name, fmt.Errorf("installed pack row is missing"),
			)
		}
		pending, err := classifyApplyAllSourceRevisionAdvance(row, advance.SourceRevisionAdvanceAuthorization)
		if err != nil {
			return applyAllSourceRevisionAdvanceCurrentSnapshot{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name, err,
			)
		}
		snapshot.entries = append(snapshot.entries, applyAllSourceRevisionAdvanceCurrentSnapshotEntry{
			advance: advance,
			row:     row,
			pending: pending,
		})
	}
	return snapshot, nil
}
