package fileshare

import (
	"context"
	"fmt"
	"log"

	"github.com/wavetermdev/waveterm/pkg/remote/awsconn"
	"github.com/wavetermdev/waveterm/pkg/remote/connparse"
	"github.com/wavetermdev/waveterm/pkg/remote/fileshare/fstype"
	"github.com/wavetermdev/waveterm/pkg/remote/fileshare/s3fs"
	"github.com/wavetermdev/waveterm/pkg/remote/fileshare/wavefs"
	"github.com/wavetermdev/waveterm/pkg/remote/fileshare/wshfs"
	"github.com/wavetermdev/waveterm/pkg/util/iochan/iochantypes"
	"github.com/wavetermdev/waveterm/pkg/wshrpc"
	"github.com/wavetermdev/waveterm/pkg/wshutil"
)

const (
	ErrorParsingConnection = "error creating fileshare client, could not parse connection %s"
)

// CreateFileShareClient creates a fileshare client based on the connection string
// Returns the client and the parsed connection
func CreateFileShareClient(ctx context.Context, connection string) (fstype.FileShareClient, *connparse.Connection) {
	conn, err := connparse.ParseURIAndReplaceCurrentHost(ctx, connection)
	if err != nil {
		log.Printf("error parsing connection: %v", err)
		return nil, nil
	}
	conntype := conn.GetType()
	if conntype == connparse.ConnectionTypeS3 {
		config, err := awsconn.GetConfig(ctx, connection)
		if err != nil {
			log.Printf("error getting aws config: %v", err)
			return nil, nil
		}
		return s3fs.NewS3Client(config), conn
	} else if conntype == connparse.ConnectionTypeWave {
		return wavefs.NewWaveClient(), conn
	} else if conntype == connparse.ConnectionTypeWsh {
		return wshfs.NewWshClient(), conn
	} else {
		log.Printf("unsupported connection type: %s", conntype)
		return nil, nil
	}
}

func Read(ctx context.Context, data wshrpc.FileData) (*wshrpc.FileData, error) {
	log.Printf("Read: %v", data.Info.Path)
	client, conn := CreateFileShareClient(ctx, data.Info.Path)
	if conn == nil || client == nil {
		return nil, fmt.Errorf(ErrorParsingConnection, data.Info.Path)
	}
	return client.Read(ctx, conn, data)
}

func ReadStream(ctx context.Context, data wshrpc.FileData) <-chan wshrpc.RespOrErrorUnion[wshrpc.FileData] {
	log.Printf("ReadStream: %v", data.Info.Path)
	client, conn := CreateFileShareClient(ctx, data.Info.Path)
	if conn == nil || client == nil {
		return wshutil.SendErrCh[wshrpc.FileData](fmt.Errorf(ErrorParsingConnection, data.Info.Path))
	}
	return client.ReadStream(ctx, conn, data)
}

func ReadTarStream(ctx context.Context, data wshrpc.CommandRemoteStreamTarData) <-chan wshrpc.RespOrErrorUnion[iochantypes.Packet] {
	log.Printf("ReadTarStream: %v", data.Path)
	client, conn := CreateFileShareClient(ctx, data.Path)
	if conn == nil || client == nil {
		return wshutil.SendErrCh[iochantypes.Packet](fmt.Errorf(ErrorParsingConnection, data.Path))
	}
	return client.ReadTarStream(ctx, conn, data.Opts)
}

func ListEntries(ctx context.Context, path string, opts *wshrpc.FileListOpts) ([]*wshrpc.FileInfo, error) {
	log.Printf("ListEntries: %v", path)
	client, conn := CreateFileShareClient(ctx, path)
	if conn == nil || client == nil {
		return nil, fmt.Errorf(ErrorParsingConnection, path)
	}
	return client.ListEntries(ctx, conn, opts)
}

func ListEntriesStream(ctx context.Context, path string, opts *wshrpc.FileListOpts) <-chan wshrpc.RespOrErrorUnion[wshrpc.CommandRemoteListEntriesRtnData] {
	log.Printf("ListEntriesStream: %v", path)
	client, conn := CreateFileShareClient(ctx, path)
	if conn == nil || client == nil {
		return wshutil.SendErrCh[wshrpc.CommandRemoteListEntriesRtnData](fmt.Errorf(ErrorParsingConnection, path))
	}
	return client.ListEntriesStream(ctx, conn, opts)
}

func Stat(ctx context.Context, path string) (*wshrpc.FileInfo, error) {
	log.Printf("Stat: %v", path)
	client, conn := CreateFileShareClient(ctx, path)
	if conn == nil || client == nil {
		return nil, fmt.Errorf(ErrorParsingConnection, path)
	}
	return client.Stat(ctx, conn)
}

func PutFile(ctx context.Context, data wshrpc.FileData) error {
	log.Printf("PutFile: %v", data.Info.Path)
	client, conn := CreateFileShareClient(ctx, data.Info.Path)
	if conn == nil || client == nil {
		return fmt.Errorf(ErrorParsingConnection, data.Info.Path)
	}
	return client.PutFile(ctx, conn, data)
}

func Mkdir(ctx context.Context, path string) error {
	log.Printf("Mkdir: %v", path)
	client, conn := CreateFileShareClient(ctx, path)
	if conn == nil || client == nil {
		return fmt.Errorf(ErrorParsingConnection, path)
	}
	return client.Mkdir(ctx, conn)
}

func Move(ctx context.Context, data wshrpc.CommandFileCopyData) error {
	opts := data.Opts
	if opts == nil {
		opts = &wshrpc.FileCopyOpts{}
	}
	log.Printf("Move: srcuri: %v, desturi: %v, opts: %v", data.SrcUri, data.DestUri, opts)
	srcClient, srcConn := CreateFileShareClient(ctx, data.SrcUri)
	if srcConn == nil || srcClient == nil {
		return fmt.Errorf("error creating fileshare client, could not parse source connection %s", data.SrcUri)
	}
	destClient, destConn := CreateFileShareClient(ctx, data.DestUri)
	if destConn == nil || destClient == nil {
		return fmt.Errorf("error creating fileshare client, could not parse destination connection %s", data.DestUri)
	}
	if srcConn.Host != destConn.Host {
		isDir, err := destClient.CopyRemote(ctx, srcConn, destConn, srcClient, opts)
		if err != nil {
			return fmt.Errorf("cannot copy %q to %q: %w", data.SrcUri, data.DestUri, err)
		}
		return srcClient.Delete(ctx, srcConn, opts.Recursive && isDir)
	} else {
		return srcClient.MoveInternal(ctx, srcConn, destConn, opts)
	}
}

func Copy(ctx context.Context, data wshrpc.CommandFileCopyData) error {
	opts := data.Opts
	if opts == nil {
		opts = &wshrpc.FileCopyOpts{}
	}
	opts.Recursive = true
	log.Printf("Copy: srcuri: %v, desturi: %v, opts: %v", data.SrcUri, data.DestUri, opts)
	srcClient, srcConn := CreateFileShareClient(ctx, data.SrcUri)
	if srcConn == nil || srcClient == nil {
		return fmt.Errorf("error creating fileshare client, could not parse source connection %s", data.SrcUri)
	}
	destClient, destConn := CreateFileShareClient(ctx, data.DestUri)
	if destConn == nil || destClient == nil {
		return fmt.Errorf("error creating fileshare client, could not parse destination connection %s", data.DestUri)
	}
	if srcConn.Host != destConn.Host {
		_, err := destClient.CopyRemote(ctx, srcConn, destConn, srcClient, opts)
		return err
	} else {
		_, err := srcClient.CopyInternal(ctx, srcConn, destConn, opts)
		return err
	}
}

func Delete(ctx context.Context, data wshrpc.CommandDeleteFileData) error {
	log.Printf("Delete: %v", data)
	client, conn := CreateFileShareClient(ctx, data.Path)
	if conn == nil || client == nil {
		return fmt.Errorf(ErrorParsingConnection, data.Path)
	}
	return client.Delete(ctx, conn, data.Recursive)
}

func Join(ctx context.Context, path string, parts ...string) (*wshrpc.FileInfo, error) {
	log.Printf("Join: %v", path)
	client, conn := CreateFileShareClient(ctx, path)
	if conn == nil || client == nil {
		return nil, fmt.Errorf(ErrorParsingConnection, path)
	}
	return client.Join(ctx, conn, parts...)
}

func Append(ctx context.Context, data wshrpc.FileData) error {
	log.Printf("Append: %v", data.Info.Path)
	client, conn := CreateFileShareClient(ctx, data.Info.Path)
	if conn == nil || client == nil {
		return fmt.Errorf(ErrorParsingConnection, data.Info.Path)
	}
	return client.AppendFile(ctx, conn, data)
}

func GetCapability(ctx context.Context, path string) (wshrpc.FileShareCapability, error) {
	log.Printf("GetCapability: %v", path)
	client, conn := CreateFileShareClient(ctx, path)
	if conn == nil || client == nil {
		return wshrpc.FileShareCapability{}, fmt.Errorf(ErrorParsingConnection, path)
	}
	return client.GetCapability(), nil
}
