package worker

import (
	"context"
	"encoding/json"
	"fmt"
	"io/fs"
	"log/slog"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/TopherMayor/unified-media-manager/internal/config"
	"github.com/TopherMayor/unified-media-manager/internal/db"
	"github.com/TopherMayor/unified-media-manager/internal/service"
)

var scannerMediaExts = map[string]bool{
	".mkv":  true,
	".mp4":  true,
	".avi":  true,
	".wmv":  true,
	".flv":  true,
	".webm": true,
	".mp3":  true,
	".flac": true,
	".m4a":  true,
	".m4b":  true,
	".ogg":  true,
	".opus": true,
	".epub": true,
	".pdf":  true,
	".mobi": true,
	".azw3": true,
}

type LibraryScanner struct {
	database   *db.DB
	matcherSvc *service.MatcherService
	mediaSvc   *service.MediaService
	cfg        *config.Config
}

func NewLibraryScanner(database *db.DB, matcherSvc *service.MatcherService, mediaSvc *service.MediaService, cfg *config.Config) *LibraryScanner {
	return &LibraryScanner{
		database:   database,
		matcherSvc: matcherSvc,
		mediaSvc:   mediaSvc,
		cfg:        cfg,
	}
}

func (w *LibraryScanner) Name() string {
	return "library_scanner"
}

func (w *LibraryScanner) CronExpr() string {
	return w.cfg.WorkerLibraryScanInterval
}

func (w *LibraryScanner) Run(ctx context.Context) error {
	ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
	defer cancel()

	scanned := 0
	tracked := 0
	matched := 0
	unmatched := 0

	rows, err := w.database.Pool.Query(ctx, "SELECT id, path, media_type FROM root_folders")
	if err != nil {
		return fmt.Errorf("query root folders: %w", err)
	}
	defer rows.Close()

	type rootFolder struct {
		id        int64
		path      string
		mediaType string
	}
	var roots []rootFolder
	for rows.Next() {
		var r rootFolder
		if err := rows.Scan(&r.id, &r.path, &r.mediaType); err != nil {
			slog.Error("failed to scan root folder", "error", err)
			continue
		}
		roots = append(roots, r)
	}

	for _, root := range roots {
		cleanRoot := filepath.Clean(root.path)

		err := filepath.WalkDir(root.path, func(path string, d fs.DirEntry, walkErr error) error {
			if walkErr != nil {
				return nil
			}
			if d.IsDir() {
				return nil
			}

			if !strings.HasPrefix(filepath.Clean(path), cleanRoot) {
				return nil
			}

			ext := filepath.Ext(path)
			if !scannerMediaExts[ext] {
				return nil
			}

			scanned++

			var exists bool
			checkErr := w.database.Pool.QueryRow(ctx,
				"SELECT EXISTS(SELECT 1 FROM media_files WHERE path = $1 AND deleted_at IS NULL)",
				path).Scan(&exists)
			if checkErr != nil {
				slog.Error("failed to check tracked file", "path", path, "error", checkErr)
				return nil
			}
			if exists {
				tracked++
				return nil
			}

			dirName := filepath.Base(filepath.Dir(path))
			fileName := filepath.Base(path)

			searchName := dirName
			if searchName == filepath.Base(root.path) {
				searchName = fileName
			}

			matchResult, matchErr := w.matcherSvc.Match(ctx, searchName, root.mediaType)
			if matchErr != nil {
				slog.Error("failed to match file", "path", path, "error", matchErr)
				unmatched++
				return nil
			}

			if matchResult.Confidence == "none" {
				unmatched++
				return nil
			}

			fileInfo, statErr := os.Stat(path)
			if statErr != nil {
				slog.Error("failed to stat file", "path", path, "error", statErr)
				unmatched++
				return nil
			}

			_, insertErr := w.database.Pool.Exec(ctx,
				`INSERT INTO media_files (media_id, media_type, path, file_name, file_size, quality, is_hardlinked)
				 VALUES ($1, $2, $3, $4, $5, $6, false)`,
				matchResult.MediaID, matchResult.MediaType, path, filepath.Base(path),
				fileInfo.Size(), json.RawMessage("{}"))
			if insertErr != nil {
				slog.Error("failed to insert media file", "path", path, "error", insertErr)
				unmatched++
				return nil
			}

			_, updateErr := w.database.Pool.Exec(ctx,
				"UPDATE media SET status = 'available' WHERE id = $1 AND status = 'unavailable'",
				matchResult.MediaID)
			if updateErr != nil {
				slog.Error("failed to update media status", "media_id", matchResult.MediaID, "error", updateErr)
			}

			matched++
			return nil
		})
		if err != nil {
			slog.Error("library scan walk error", "root", root.path, "error", err)
		}
	}

	slog.Info("library scan completed", "scanned", scanned, "tracked", tracked, "matched", matched, "unmatched", unmatched)
	return nil
}
