package drive

import (
	"context"
	"errors"
	"fmt"
	"io"
	"path"
	"strings"

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

	"github.com/ultisuite/ulti-backend/internal/api/paginate"
	"github.com/ultisuite/ulti-backend/internal/api/query"
	"github.com/ultisuite/ulti-backend/internal/driveroot"
	"github.com/ultisuite/ulti-backend/internal/drivestore"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
)

type resolvedRoot struct {
	ref        driveroot.Ref
	davPath    string
	logicalDir string
	ncUserID   string
}

func (s *Service) ensureStore() *drivestore.Store {
	if s.store == nil && s.db != nil {
		s.store = drivestore.NewStore(s.db)
	}
	return s.store
}

func (s *Service) resolveRoot(ctx context.Context, ncUserID string, ref driveroot.Ref) (resolvedRoot, error) {
	ref.Path = nextcloud.NormalizeClientPath(ref.Path)
	switch ref.Kind {
	case driveroot.KindPersonal, "":
		return resolvedRoot{
			ref:        driveroot.Personal(ref.Path),
			davPath:    s.nc.WebDAVPath(ncUserID, ref.Path),
			logicalDir: ref.Path,
			ncUserID:   ncUserID,
		}, nil
	case driveroot.KindOrg:
		store := s.ensureStore()
		if store == nil {
			return resolvedRoot{}, ErrInvalid
		}
		folder, err := store.GetOrgFolder(ctx, ref.RootID)
		if err != nil {
			if errors.Is(err, drivestore.ErrOrgFolderNotFound) {
				return resolvedRoot{}, ErrNotFound
			}
			return resolvedRoot{}, err
		}
		return resolvedRoot{
			ref:        driveroot.Org(ref.RootID, ref.Path),
			davPath:    nextcloud.GroupFolderWebDAVPath(folder.NCFolderID, ref.Path),
			logicalDir: ref.Path,
			ncUserID:   ncUserID,
		}, nil
	case driveroot.KindMount:
		store := s.ensureStore()
		if store == nil {
			return resolvedRoot{}, ErrInvalid
		}
		mount, err := store.GetMount(ctx, ref.RootID)
		if err != nil {
			if errors.Is(err, drivestore.ErrMountNotFound) {
				return resolvedRoot{}, ErrNotFound
			}
			return resolvedRoot{}, err
		}
		fullPath := joinMountPath(mount.MountPoint, ref.Path)
		return resolvedRoot{
			ref:        driveroot.Mount(ref.RootID, ref.Path),
			davPath:    s.nc.WebDAVPath(ncUserID, fullPath),
			logicalDir: ref.Path,
			ncUserID:   ncUserID,
		}, nil
	default:
		return resolvedRoot{}, ErrInvalid
	}
}

func joinMountPath(mountPoint, logicalPath string) string {
	mp := strings.Trim(mountPoint, "/")
	lp := strings.Trim(logicalPath, "/")
	if mp == "" {
		if lp == "" {
			return "/"
		}
		return "/" + lp
	}
	if lp == "" {
		return "/" + mp
	}
	return "/" + mp + "/" + lp
}

func (s *Service) resolveFileRoot(ctx context.Context, ncUserID string, ref driveroot.Ref) (resolvedRoot, error) {
	resolved, err := s.resolveRoot(ctx, ncUserID, ref)
	if err != nil {
		return resolvedRoot{}, err
	}
	if ref.Path != "/" && !strings.HasSuffix(resolved.ref.Path, "/") {
		return resolved, nil
	}
	return resolved, nil
}

func (s *Service) resolveFileDAV(ctx context.Context, ncUserID string, ref driveroot.Ref) (resolvedRoot, string, error) {
	base, err := s.resolveRoot(ctx, ncUserID, ref)
	if err != nil {
		return resolvedRoot{}, "", err
	}
	filePath := nextcloud.NormalizeClientPath(ref.Path)
	if filePath == "/" {
		return base, base.davPath, nil
	}
	davPath := base.davPath
	if !strings.HasSuffix(davPath, "/") && filePath != "/" {
		// base.davPath is directory; append file segment if needed
		rel := strings.TrimPrefix(filePath, "/")
		if base.ref.Path == "/" || strings.HasPrefix(filePath, base.ref.Path+"/") || base.ref.Path == filePath {
			// already included in path from ref
		}
		_ = rel
	}
	// For file operations, ref.Path is full logical path within root
	switch base.ref.Kind {
	case driveroot.KindOrg:
		folder, _ := s.ensureStore().GetOrgFolder(ctx, ref.RootID)
		davPath = nextcloud.GroupFolderWebDAVPath(folder.NCFolderID, filePath)
	case driveroot.KindMount:
		mount, _ := s.ensureStore().GetMount(ctx, ref.RootID)
		davPath = s.nc.WebDAVPath(ncUserID, joinMountPath(mount.MountPoint, filePath))
	default:
		davPath = s.nc.WebDAVPath(ncUserID, filePath)
	}
	return base, davPath, nil
}

func (s *Service) ListFilesAtRoot(ctx context.Context, ncUserID string, ref driveroot.Ref, params query.ListParams) (FilesList, error) {
	resolved, err := s.resolveRoot(ctx, ncUserID, ref)
	if err != nil {
		return FilesList{}, err
	}
	var files []nextcloud.FileInfo
	if ref.Kind == driveroot.KindPersonal || ref.Kind == "" {
		files, err = s.nc.ListFiles(ctx, ncUserID, ref.Path)
	} else {
		files, err = s.nc.ListFilesAtDAV(ctx, ncUserID, resolved.davPath, ref.Path)
	}
	if err != nil {
		return FilesList{}, mapDriveError(err)
	}
	files = driveroot.EnrichFiles(files, resolved.ref)
	if ref.Kind == driveroot.KindMount {
		mount, mountErr := s.ensureStore().GetMount(ctx, ref.RootID)
		if mountErr == nil {
			files = driveroot.EnrichMountCloudNativeFiles(files, mount.BackendType)
		}
	}
	filtered := visibleDriveFiles(files, params.Q)
	page, total := paginate.Slice(filtered, params.Offset(), params.Limit())
	return FilesList{
		Files:      page,
		Pagination: params.Meta(&total),
	}, nil
}

func (s *Service) StatFileAtRoot(ctx context.Context, ncUserID string, ref driveroot.Ref) (nextcloud.FileInfo, error) {
	_, davPath, err := s.resolveFileDAV(ctx, ncUserID, ref)
	if err != nil {
		return nextcloud.FileInfo{}, err
	}
	var file nextcloud.FileInfo
	if ref.Kind == driveroot.KindPersonal || ref.Kind == "" {
		file, err = s.nc.StatFile(ctx, ncUserID, ref.Path)
	} else {
		file, err = s.nc.StatFileAtDAV(ctx, ncUserID, davPath, ref.Path)
	}
	if err != nil {
		return nextcloud.FileInfo{}, mapDriveError(err)
	}
	file = driveroot.EnrichFile(file, ref)
	if ref.Kind == driveroot.KindMount {
		mount, mountErr := s.ensureStore().GetMount(ctx, ref.RootID)
		if mountErr == nil {
			driveroot.EnrichMountCloudNative(&file, mount.BackendType)
		}
	}
	return file, nil
}

func (s *Service) UploadAtRoot(ctx context.Context, ncUserID string, ref driveroot.Ref, content io.Reader, contentType string, contentLength int64) error {
	_, davPath, err := s.resolveFileDAV(ctx, ncUserID, ref)
	if err != nil {
		return err
	}
	if ref.Kind == driveroot.KindPersonal || ref.Kind == "" {
		return s.Upload(ctx, ncUserID, ref.Path, content, contentType, contentLength)
	}
	return mapDriveError(s.nc.UploadAtDAV(ctx, ncUserID, davPath, contentType, content))
}

func (s *Service) DeleteAtRoot(ctx context.Context, ncUserID string, ref driveroot.Ref) error {
	_, davPath, err := s.resolveFileDAV(ctx, ncUserID, ref)
	if err != nil {
		return err
	}
	if ref.Kind == driveroot.KindPersonal || ref.Kind == "" {
		return s.Delete(ctx, ncUserID, ref.Path)
	}
	return mapDriveError(s.nc.DeleteAtDAV(ctx, ncUserID, davPath))
}

func (s *Service) CreateFolderAtRoot(ctx context.Context, ncUserID string, ref driveroot.Ref) error {
	_, davPath, err := s.resolveFileDAV(ctx, ncUserID, ref)
	if err != nil {
		return err
	}
	if ref.Kind == driveroot.KindPersonal || ref.Kind == "" {
		return s.CreateFolder(ctx, ncUserID, ref.Path)
	}
	return mapDriveError(s.nc.CreateFolderAtDAV(ctx, ncUserID, davPath))
}

func (s *Service) DownloadAtRoot(ctx context.Context, ncUserID string, ref driveroot.Ref) (io.ReadCloser, string, error) {
	_, davPath, err := s.resolveFileDAV(ctx, ncUserID, ref)
	if err != nil {
		return nil, "", err
	}
	if ref.Kind == driveroot.KindPersonal || ref.Kind == "" {
		return s.Download(ctx, ncUserID, ref.Path)
	}
	body, contentType, err := s.nc.DownloadAtDAV(ctx, ncUserID, davPath)
	if err != nil {
		return nil, "", mapDriveError(err)
	}
	return body, contentType, nil
}

func (s *Service) MoveAtRoot(ctx context.Context, ncUserID string, source, destination driveroot.Ref) error {
	srcDAV, err := s.davPathForRef(ctx, ncUserID, source)
	if err != nil {
		return err
	}
	destDAV, err := s.davPathForRef(ctx, ncUserID, destination)
	if err != nil {
		return err
	}
	if source.Kind == driveroot.KindPersonal && destination.Kind == driveroot.KindPersonal {
		return s.Move(ctx, ncUserID, source.Path, destination.Path)
	}
	return mapDriveError(s.nc.MoveAtDAV(ctx, ncUserID, srcDAV, destDAV))
}

func (s *Service) CopyAtRoot(ctx context.Context, ncUserID string, source, destination driveroot.Ref) error {
	srcDAV, err := s.davPathForRef(ctx, ncUserID, source)
	if err != nil {
		return err
	}
	destDAV, err := s.davPathForRef(ctx, ncUserID, destination)
	if err != nil {
		return err
	}
	if source.Kind == driveroot.KindPersonal && destination.Kind == driveroot.KindPersonal {
		return s.Copy(ctx, ncUserID, source.Path, destination.Path)
	}
	return mapDriveError(s.nc.CopyAtDAV(ctx, ncUserID, srcDAV, destDAV))
}

func (s *Service) RenameAtRoot(ctx context.Context, ncUserID string, ref driveroot.Ref, newName string) error {
	if strings.Contains(newName, "/") {
		return ErrInvalid
	}
	dir := path.Dir(strings.TrimPrefix(ref.Path, "/"))
	if dir == "." {
		dir = ""
	}
	destPath := "/" + strings.Trim(newName, "/")
	if dir != "" {
		destPath = "/" + dir + destPath
	}
	dest := driveroot.Ref{Kind: ref.Kind, RootID: ref.RootID, Path: destPath}
	return s.MoveAtRoot(ctx, ncUserID, ref, dest)
}

func (s *Service) davPathForRef(ctx context.Context, ncUserID string, ref driveroot.Ref) (string, error) {
	_, davPath, err := s.resolveFileDAV(ctx, ncUserID, ref)
	return davPath, err
}

func (s *Service) platformUserID(ctx context.Context, externalID string) (string, error) {
	if s.db == nil {
		return "", fmt.Errorf("database not configured")
	}
	var id string
	err := s.db.QueryRow(ctx, `SELECT id::text FROM users WHERE external_id = $1`, externalID).Scan(&id)
	if err != nil {
		return "", err
	}
	return id, nil
}

func extendServiceStore(s *Service, db *pgxpool.Pool) {
	if s != nil && s.store == nil && db != nil {
		s.store = drivestore.NewStore(db)
	}
}
