package buildflags

import (
	"cmp"
	"encoding/json"
	"slices"
	"strings"

	controllerapi "github.com/docker/buildx/controller/pb"
	"github.com/moby/buildkit/util/gitutil"
)

type SSHKeys []*SSH

func (s SSHKeys) Merge(other SSHKeys) SSHKeys {
	if other == nil {
		s.Normalize()
		return s
	} else if s == nil {
		other.Normalize()
		return other
	}

	return append(s, other...).Normalize()
}

func (s SSHKeys) Normalize() SSHKeys {
	if len(s) == 0 {
		return nil
	}
	return removeSSHDupes(s)
}

func (s SSHKeys) ToPB() []*controllerapi.SSH {
	if len(s) == 0 {
		return nil
	}

	entries := make([]*controllerapi.SSH, len(s))
	for i, entry := range s {
		entries[i] = entry.ToPB()
	}
	return entries
}

type SSH struct {
	ID    string   `json:"id,omitempty" cty:"id"`
	Paths []string `json:"paths,omitempty" cty:"paths"`
}

func (s *SSH) Equal(other *SSH) bool {
	return s.Less(other) == 0
}

func (s *SSH) Less(other *SSH) int {
	if s.ID != other.ID {
		return cmp.Compare(s.ID, other.ID)
	}
	return slices.Compare(s.Paths, other.Paths)
}

func (s *SSH) String() string {
	if len(s.Paths) == 0 {
		return s.ID
	}

	var b csvBuilder
	paths := strings.Join(s.Paths, ",")
	b.Write(s.ID, paths)
	return b.String()
}

func (s *SSH) ToPB() *controllerapi.SSH {
	return &controllerapi.SSH{
		ID:    s.ID,
		Paths: s.Paths,
	}
}

func (s *SSH) UnmarshalJSON(data []byte) error {
	var v struct {
		ID    string   `json:"id,omitempty"`
		Paths []string `json:"paths,omitempty"`
	}
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	s.ID = v.ID
	s.Paths = v.Paths
	return nil
}

func (s *SSH) UnmarshalText(text []byte) error {
	parts := strings.SplitN(string(text), "=", 2)

	s.ID = parts[0]
	if len(parts) > 1 {
		s.Paths = strings.Split(parts[1], ",")
	} else {
		s.Paths = nil
	}
	return nil
}

func ParseSSHSpecs(sl []string) ([]*controllerapi.SSH, error) {
	var outs []*controllerapi.SSH
	if len(sl) == 0 {
		return nil, nil
	}

	for _, s := range sl {
		if s == "" {
			continue
		}

		var out SSH
		if err := out.UnmarshalText([]byte(s)); err != nil {
			return nil, err
		}
		outs = append(outs, out.ToPB())
	}
	return outs, nil
}

// IsGitSSH returns true if the given repo URL is accessed over ssh
func IsGitSSH(repo string) bool {
	url, err := gitutil.ParseURL(repo)
	if err != nil {
		return false
	}
	return url.Scheme == gitutil.SSHProtocol
}

func removeSSHDupes(s []*SSH) []*SSH {
	var res []*SSH
	m := map[string]int{}
	for _, ssh := range s {
		if i, ok := m[ssh.ID]; ok {
			res[i] = ssh
		} else {
			m[ssh.ID] = len(res)
			res = append(res, ssh)
		}
	}
	return res
}
