package file

import (
	"context"
	"fmt"
	"path/filepath"
	"strings"
	"time"

	"github.com/stashapp/stash/pkg/logger"
	"github.com/stashapp/stash/pkg/models"
)

// GetOrCreateFolderHierarchy gets the folder for the given path, or creates a folder hierarchy for the given path if one if no existing folder is found.
// Does not create any folders in the file system
func GetOrCreateFolderHierarchy(ctx context.Context, fc models.FolderFinderCreator, path string) (*models.Folder, error) {
	// get or create folder hierarchy
	folder, err := fc.FindByPath(ctx, path)
	if err != nil {
		return nil, err
	}

	if folder == nil {
		parentPath := filepath.Dir(path)
		parent, err := GetOrCreateFolderHierarchy(ctx, fc, parentPath)
		if err != nil {
			return nil, err
		}

		now := time.Now()

		folder = &models.Folder{
			Path:           path,
			ParentFolderID: &parent.ID,
			DirEntry:       models.DirEntry{
				// leave mod time empty for now - it will be updated when the folder is scanned
			},
			CreatedAt: now,
			UpdatedAt: now,
		}

		if err = fc.Create(ctx, folder); err != nil {
			return nil, fmt.Errorf("creating folder %s: %w", path, err)
		}
	}

	return folder, nil
}

func transferZipHierarchy(ctx context.Context, folderStore models.FolderReaderWriter, files models.FileFinderUpdater, zipFileID models.FileID, oldPath string, newPath string) error {
	if err := transferZipFolderHierarchy(ctx, folderStore, zipFileID, oldPath, newPath); err != nil {
		return fmt.Errorf("moving folder hierarchy for file %s: %w", oldPath, err)
	}

	if err := transferZipFileEntries(ctx, folderStore, files, zipFileID, oldPath, newPath); err != nil {
		return fmt.Errorf("moving zip file contents for file %s: %w", oldPath, err)
	}

	return nil
}

// transferZipFolderHierarchy creates the folder hierarchy for zipFileID under newPath, and removes
// ZipFileID from folders under oldPath.
func transferZipFolderHierarchy(ctx context.Context, folderStore models.FolderReaderWriter, zipFileID models.FileID, oldPath string, newPath string) error {
	zipFolders, err := folderStore.FindByZipFileID(ctx, zipFileID)
	if err != nil {
		return err
	}

	for _, oldFolder := range zipFolders {
		oldZfPath := oldFolder.Path

		// sanity check - ignore folders which aren't under oldPath
		if !strings.HasPrefix(oldZfPath, oldPath) {
			continue
		}

		relZfPath, err := filepath.Rel(oldPath, oldZfPath)
		if err != nil {
			return err
		}
		newZfPath := filepath.Join(newPath, relZfPath)

		newFolder, err := GetOrCreateFolderHierarchy(ctx, folderStore, newZfPath)
		if err != nil {
			return err
		}

		// add ZipFileID to new folder
		logger.Debugf("adding zip file %s to folder %s", zipFileID, newFolder.Path)
		newFolder.ZipFileID = &zipFileID
		if err = folderStore.Update(ctx, newFolder); err != nil {
			return err
		}

		// remove ZipFileID from old folder
		logger.Debugf("removing zip file %s from folder %s", zipFileID, oldFolder.Path)
		oldFolder.ZipFileID = nil
		if err = folderStore.Update(ctx, oldFolder); err != nil {
			return err
		}
	}

	return nil
}

func transferZipFileEntries(ctx context.Context, folders models.FolderFinderCreator, files models.FileFinderUpdater, zipFileID models.FileID, oldPath, newPath string) error {
	// move contained files if file is a zip file
	zipFiles, err := files.FindByZipFileID(ctx, zipFileID)
	if err != nil {
		return fmt.Errorf("finding contained files in file %s: %w", oldPath, err)
	}
	for _, zf := range zipFiles {
		zfBase := zf.Base()
		oldZfPath := zfBase.Path
		oldZfDir := filepath.Dir(oldZfPath)

		// sanity check - ignore files which aren't under oldPath
		if !strings.HasPrefix(oldZfPath, oldPath) {
			continue
		}

		relZfDir, err := filepath.Rel(oldPath, oldZfDir)
		if err != nil {
			return fmt.Errorf("moving contained file %s: %w", zfBase.ID, err)
		}
		newZfDir := filepath.Join(newPath, relZfDir)

		// folder should have been created by transferZipFolderHierarchy
		newZfFolder, err := GetOrCreateFolderHierarchy(ctx, folders, newZfDir)
		if err != nil {
			return fmt.Errorf("getting or creating folder hierarchy: %w", err)
		}

		// update file parent folder
		zfBase.ParentFolderID = newZfFolder.ID
		logger.Debugf("moving %s to folder %s", zfBase.Path, newZfFolder.Path)
		if err := files.Update(ctx, zf); err != nil {
			return fmt.Errorf("updating file %s: %w", oldZfPath, err)
		}
	}

	return nil
}
