package proton_api_bridge

import (
	"context"
	"time"

	"github.com/rclone/go-proton-api"
)

type ProtonDirectoryData struct {
	Link     *proton.Link
	Name     string
	IsFolder bool
}

func (protonDrive *ProtonDrive) ListDirectory(
	ctx context.Context,
	folderLinkID string) ([]*ProtonDirectoryData, error) {
	ret := make([]*ProtonDirectoryData, 0)

	folderLink, err := protonDrive.getLink(ctx, folderLinkID)
	if err != nil {
		return nil, err
	}

	if folderLink.State == proton.LinkStateActive {
		childrenLinks, err := protonDrive.c.ListChildren(ctx, protonDrive.MainShare.ShareID, folderLink.LinkID, true)
		if err != nil {
			return nil, err
		}

		if childrenLinks != nil {
			folderParentKR, err := protonDrive.getLinkKRByID(ctx, folderLink.ParentLinkID)
			if err != nil {
				return nil, err
			}
			signatureVerificationKR, err := protonDrive.getSignatureVerificationKeyring([]string{folderLink.SignatureEmail})
			if err != nil {
				return nil, err
			}
			folderLinkKR, err := folderLink.GetKeyRing(folderParentKR, signatureVerificationKR)
			if err != nil {
				return nil, err
			}

			for i := range childrenLinks {
				if childrenLinks[i].State != proton.LinkStateActive {
					continue
				}

				signatureVerificationKR, err := protonDrive.getSignatureVerificationKeyring([]string{childrenLinks[i].NameSignatureEmail, childrenLinks[i].SignatureEmail})
				if err != nil {
					return nil, err
				}
				name, err := childrenLinks[i].GetName(folderLinkKR, signatureVerificationKR)
				if err != nil {
					return nil, err
				}
				ret = append(ret, &ProtonDirectoryData{
					Link:     &childrenLinks[i],
					Name:     name,
					IsFolder: childrenLinks[i].Type == proton.LinkTypeFolder,
				})
			}
		}
	}

	return ret, nil
}

func (protonDrive *ProtonDrive) CreateNewFolderByID(ctx context.Context, parentLinkID string, folderName string) (string, error) {
	/* It's like event system, we need to get the latest information before creating the move request! */
	protonDrive.removeLinkIDFromCache(parentLinkID, false)

	parentLink, err := protonDrive.getLink(ctx, parentLinkID)
	if err != nil {
		return "", err
	}

	return protonDrive.CreateNewFolder(ctx, parentLink, folderName)
}

func (protonDrive *ProtonDrive) CreateNewFolder(ctx context.Context, parentLink *proton.Link, folderName string) (string, error) {
	parentNodeKR, err := protonDrive.getLinkKR(ctx, parentLink)
	if err != nil {
		return "", err
	}

	// Folder node keys stay v4 (non-AEAD) per Proton's crypto-refresh design.
	newNodeKey, newNodePassphraseEnc, newNodePassphraseSignature, err := generateNodeKeys(parentNodeKR, protonDrive.DefaultAddrKR, false)
	if err != nil {
		return "", err
	}

	createFolderReq := proton.CreateFolderReq{
		ParentLinkID: parentLink.LinkID,

		// Name string
		// Hash string

		NodeKey: newNodeKey,
		// NodeHashKey string

		NodePassphrase:          newNodePassphraseEnc,
		NodePassphraseSignature: newNodePassphraseSignature,

		SignatureAddress: protonDrive.signatureAddress,
	}

	/* Name is encrypted using the parent's keyring, and signed with address key */
	err = createFolderReq.SetName(folderName, protonDrive.DefaultAddrKR, parentNodeKR)
	if err != nil {
		return "", err
	}

	signatureVerificationKR, err := protonDrive.getSignatureVerificationKeyring([]string{parentLink.SignatureEmail}, parentNodeKR)
	if err != nil {
		return "", err
	}
	parentHashKey, err := parentLink.GetHashKey(parentNodeKR, signatureVerificationKR)
	if err != nil {
		return "", err
	}
	err = createFolderReq.SetHash(folderName, parentHashKey)
	if err != nil {
		return "", err
	}

	newNodeKR, err := getKeyRing(parentNodeKR, protonDrive.DefaultAddrKR, newNodeKey, newNodePassphraseEnc, newNodePassphraseSignature)
	if err != nil {
		return "", err
	}
	err = createFolderReq.SetNodeHashKey(newNodeKR)
	if err != nil {
		return "", err
	}

	// FIXME: check for duplicated filename by relying on checkAvailableHashes
	// if the folder name already exist, this call will return an error
	createFolderResp, err := protonDrive.c.CreateFolder(ctx, protonDrive.MainShare.ShareID, createFolderReq)
	if err != nil {
		return "", err
	}
	// log.Printf("createFolderResp %#v", createFolderResp)

	return createFolderResp.ID, nil
}

func (protonDrive *ProtonDrive) MoveFileByID(ctx context.Context, srcLinkID, dstParentLinkID string, dstName string) error {
	/* It's like event system, we need to get the latest information before creating the move request! */
	protonDrive.removeLinkIDFromCache(srcLinkID, false)

	srcLink, err := protonDrive.getLink(ctx, srcLinkID)
	if err != nil {
		return err
	}
	if srcLink.State != proton.LinkStateActive {
		return ErrLinkMustBeActive
	}

	dstParentLink, err := protonDrive.getLink(ctx, dstParentLinkID)
	if err != nil {
		return err
	}
	if dstParentLink.State != proton.LinkStateActive {
		return ErrLinkMustBeActive
	}

	return protonDrive.MoveFile(ctx, srcLink, dstParentLink, dstName)
}

func (protonDrive *ProtonDrive) MoveFile(ctx context.Context, srcLink *proton.Link, dstParentLink *proton.Link, dstName string) error {
	return protonDrive.moveLink(ctx, srcLink, dstParentLink, dstName)
}

func (protonDrive *ProtonDrive) MoveFolderByID(ctx context.Context, srcLinkID, dstParentLinkID, dstName string) error {
	/* It's like event system, we need to get the latest information before creating the move request! */
	protonDrive.removeLinkIDFromCache(srcLinkID, false)

	srcLink, err := protonDrive.getLink(ctx, srcLinkID)
	if err != nil {
		return err
	}
	if srcLink.State != proton.LinkStateActive {
		return ErrLinkMustBeActive
	}

	dstParentLink, err := protonDrive.getLink(ctx, dstParentLinkID)
	if err != nil {
		return err
	}
	if dstParentLink.State != proton.LinkStateActive {
		return ErrLinkMustBeActive
	}

	return protonDrive.MoveFolder(ctx, srcLink, dstParentLink, dstName)
}

func (protonDrive *ProtonDrive) MoveFolder(ctx context.Context, srcLink *proton.Link, dstParentLink *proton.Link, dstName string) error {
	return protonDrive.moveLink(ctx, srcLink, dstParentLink, dstName)
}

func (protonDrive *ProtonDrive) moveLink(ctx context.Context, srcLink *proton.Link, dstParentLink *proton.Link, dstName string) error {
	// we are moving the srcLink to under dstParentLink, with name dstName
	req := proton.MoveLinkReq{
		ParentLinkID:       dstParentLink.LinkID,
		OriginalHash:       srcLink.Hash,
		NameSignatureEmail: protonDrive.signatureAddress,
	}

	dstParentKR, err := protonDrive.getLinkKR(ctx, dstParentLink)
	if err != nil {
		return err
	}

	err = req.SetName(dstName, protonDrive.DefaultAddrKR, dstParentKR)
	if err != nil {
		return err
	}

	signatureVerificationKR, err := protonDrive.getSignatureVerificationKeyring([]string{dstParentLink.SignatureEmail}, dstParentKR)
	if err != nil {
		return err
	}
	dstParentHashKey, err := dstParentLink.GetHashKey(dstParentKR, signatureVerificationKR)
	if err != nil {
		return err
	}
	err = req.SetHash(dstName, dstParentHashKey)
	if err != nil {
		return err
	}

	srcParentKR, err := protonDrive.getLinkKRByID(ctx, srcLink.ParentLinkID)
	if err != nil {
		return err
	}
	nodePassphrase, err := reencryptKeyPacket(srcParentKR, dstParentKR, protonDrive.DefaultAddrKR, srcLink.NodePassphrase)
	if err != nil {
		return err
	}
	req.NodePassphrase = nodePassphrase

	// NodePassphraseSignature/SignatureEmail are only required for
	// anonymously created nodes (matching the official Proton Drive Windows
	// client). For regular user-owned nodes they are omitted so the server
	// preserves the existing signature on the link.

	protonDrive.removeLinkIDFromCache(srcLink.LinkID, false)

	// TODO: disable cache when move is in action?
	// because there might be the case where others read for the same link currently being move -> race condition
	// argument: cache itself is already outdated in a sense, as we don't even have event system (even if we have, it's still outdated...)
	err = protonDrive.c.MoveLink(ctx, protonDrive.MainShare.ShareID, srcLink.LinkID, req)
	if err != nil {
		return err
	}

	time.Sleep(5 * time.Second)

	return nil
}
