package library

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"os"

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

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

// ErrTrackNotFound is returned when DeleteTrackFile is called with an id
// that has no row in tracks.
var ErrTrackNotFound = errors.New("library: track not found")

// DeleteTrackFile removes a track file from disk and its row from the
// tracks table. Album and artist rows are left untouched.
//
// Steps:
//  1. Look up the track to get its file_path.
//  2. Remove the file from disk. fs.ErrNotExist is OK — already gone.
//  3. Delete the tracks row.
//
// Order matters: file first, then DB. If the file delete fails (permission,
// I/O error), we leave the DB row alone so the admin can retry.
//
// The reverse failure mode — file gone, DB row still present — IS reconciled
// now, and not by this function: the scan's reconcile pass stamps
// tracks.missing_since (#2523), every selection path filters on it, and a file
// that returns is un-marked or adopted at its new path (#2528). That is the
// normal life of a vanished file and it is deliberately non-destructive: the
// row, its play history and its likes survive, because a missing file is a
// track Minstrel still knows about (#2527).
//
// So this function is NOT the missing-file path. It is the explicit admin
// action "remove this recording from disk and from the library", and it is
// irreversible: tracks CASCADEs to play_events, general_likes_tracks,
// contextual_likes, track_tags and playback_errors. Reach for it when the
// operator means to destroy the record, never to tidy up a row whose file
// merely went away.
func DeleteTrackFile(ctx context.Context, pool *pgxpool.Pool, trackID pgtype.UUID) error {
	q := dbq.New(pool)
	track, err := q.GetTrackByID(ctx, trackID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return ErrTrackNotFound
		}
		return fmt.Errorf("get track: %w", err)
	}

	if err := os.Remove(track.FilePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
		return fmt.Errorf("remove file: %w", err)
	}

	if _, err := pool.Exec(ctx, "DELETE FROM tracks WHERE id = $1", trackID); err != nil {
		return fmt.Errorf("delete row: %w", err)
	}
	// Log the change after the delete succeeds. Best-effort: a Warn-level
	// failure here would leave the cache index orphaned on offline clients
	// until the next scan touches the surrounding album.
	if err := syncpkg.LogChange(ctx, pool, syncpkg.EntityTrack,
		syncpkg.FormatUUID(trackID), syncpkg.OpDelete); err != nil {
		return fmt.Errorf("log change: %w", err)
	}
	return nil
}
