package sync

import (
	"context"
	"fmt"

	"github.com/jackc/pgx/v5/pgtype"

	"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)

// LogChange writes a row to library_changes via the supplied DBTX. dbq.DBTX
// is satisfied by both *pgxpool.Pool and pgx.Tx, so callers can opt into
// transactional safety where they already hold a tx, or pool-bind for
// best-effort logging when no tx is available.
//
// Callers SHOULD pass a tx that owns the underlying mutation — the change
// row and the mutation then commit or roll back together. When pool-bound,
// the scanner-style "log after success" pattern keeps the log consistent
// with reality at the cost of a tiny race window: if the LogChange call
// itself fails after the mutation succeeded, that mutation won't appear in
// the change log until the entity is touched again.
//
// entityID is the string form of the row's primary key. For composite-key
// entities (likes, playlist_tracks), use EncodeLikeID / EncodePlaylistTrackID
// to produce stable strings. For pgtype.UUID values, use FormatUUID.
func LogChange(ctx context.Context, dbtx dbq.DBTX, entityType EntityType, entityID string, op Op) error {
	q := dbq.New(dbtx)
	if err := q.InsertLibraryChange(ctx, dbq.InsertLibraryChangeParams{
		EntityType: string(entityType),
		EntityID:   entityID,
		Op:         string(op),
	}); err != nil {
		return fmt.Errorf("sync.LogChange(%s, %s, %s): %w", entityType, entityID, op, err)
	}
	return nil
}

// FormatUUID renders a pgtype.UUID as the canonical 8-4-4-4-12 hex form.
// Returns "" if the UUID is not valid.
func FormatUUID(u pgtype.UUID) string {
	if !u.Valid {
		return ""
	}
	return formatUUIDBytes(u.Bytes)
}

func formatUUIDBytes(b [16]byte) string {
	const hex = "0123456789abcdef"
	out := make([]byte, 36)
	pos := 0
	for i := 0; i < 16; i++ {
		if i == 4 || i == 6 || i == 8 || i == 10 {
			out[pos] = '-'
			pos++
		}
		out[pos] = hex[b[i]>>4]
		out[pos+1] = hex[b[i]&0x0f]
		pos += 2
	}
	return string(out)
}

// EncodeLikeID joins a user UUID and an entity UUID into the stable
// composite identifier used for like_* rows in library_changes. Both
// sides are UUID strings so no escaping is needed.
func EncodeLikeID(userID, entityID string) string {
	return userID + ":" + entityID
}

// EncodePlaylistTrackID joins a playlist UUID and a track UUID into the
// stable composite identifier used for playlist_track rows.
func EncodePlaylistTrackID(playlistID, trackID string) string {
	return playlistID + ":" + trackID
}
