package sftp

import (
	"fmt"
	"path"

	"github.com/OpenListTeam/OpenList/v4/pkg/singleflight"
	"github.com/pkg/sftp"
	log "github.com/sirupsen/logrus"
	"golang.org/x/crypto/ssh"
)

// do others that not defined in Driver interface

func (d *SFTP) initClient() error {
	_, err, _ := singleflight.AnyGroup.Do(fmt.Sprintf("SFTP.initClient:%p", d), func() (any, error) {
		return nil, d._initClient()
	})
	return err
}
func (d *SFTP) _initClient() error {
	var auth ssh.AuthMethod
	if len(d.PrivateKey) > 0 {
		var err error
		var signer ssh.Signer
		if len(d.Passphrase) > 0 {
			signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(d.PrivateKey), []byte(d.Passphrase))
		} else {
			signer, err = ssh.ParsePrivateKey([]byte(d.PrivateKey))
		}
		if err != nil {
			return err
		}
		auth = ssh.PublicKeys(signer)
	} else {
		auth = ssh.Password(d.Password)
	}
	config := &ssh.ClientConfig{
		User:            d.Username,
		Auth:            []ssh.AuthMethod{auth},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
	conn, err := ssh.Dial("tcp", d.Address, config)
	if err != nil {
		return err
	}
	d.client, err = sftp.NewClient(conn)
	if err == nil {
		d.clientConnectionError = nil
		go func(d *SFTP) {
			d.clientConnectionError = d.client.Wait()
		}(d)
	}
	return err
}

func (d *SFTP) clientReconnectOnConnectionError() error {
	err := d.clientConnectionError
	if err == nil {
		return nil
	}
	log.Debugf("[sftp] discarding closed sftp connection: %v", err)
	if d.client != nil {
		_ = d.client.Close()
	}
	err = d.initClient()
	return err
}

func (d *SFTP) remove(remotePath string) error {
	f, err := d.client.Stat(remotePath)
	if err != nil {
		return nil
	}
	if f.IsDir() {
		return d.removeDirectory(remotePath)
	} else {
		return d.removeFile(remotePath)
	}
}

func (d *SFTP) removeDirectory(remotePath string) error {
	remoteFiles, err := d.client.ReadDir(remotePath)
	if err != nil {
		return err
	}
	for _, backupDir := range remoteFiles {
		remoteFilePath := path.Join(remotePath, backupDir.Name())
		if backupDir.IsDir() {
			err := d.removeDirectory(remoteFilePath)
			if err != nil {
				return err
			}
		} else {
			err := d.removeFile(remoteFilePath)
			if err != nil {
				return err
			}
		}
	}
	return d.client.RemoveDirectory(remotePath)
}

func (d *SFTP) removeFile(remotePath string) error {
	return d.client.Remove(path.Join(remotePath))
}
