// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package registry

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/docker/cli/cli/config/configfile"
	"github.com/docker/cli/cli/config/types"

	"go.woodpecker-ci.org/woodpecker/v3/server/model"
	store_types "go.woodpecker-ci.org/woodpecker/v3/server/store/types"
)

type filesystem struct {
	path string
}

func NewFilesystem(path string) ReadOnlyService {
	return &filesystem{path}
}

func parseDockerConfig(path string) ([]*model.Registry, error) {
	if path == "" {
		return nil, nil
	}

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	configFile := configfile.ConfigFile{
		AuthConfigs: make(map[string]types.AuthConfig),
	}

	if err := json.NewDecoder(f).Decode(&configFile); err != nil {
		return nil, err
	}

	for registryHostname := range configFile.CredentialHelpers {
		newAuth, err := configFile.GetAuthConfig(registryHostname)
		if err == nil {
			configFile.AuthConfigs[registryHostname] = newAuth
		}
	}

	for addr, ac := range configFile.AuthConfigs {
		if ac.Auth != "" {
			ac.Username, ac.Password, err = decodeAuth(ac.Auth)
			if err != nil {
				return nil, err
			}
			ac.Auth = ""
			ac.ServerAddress = addr
			configFile.AuthConfigs[addr] = ac
		}
	}

	var registries []*model.Registry
	for key, auth := range configFile.AuthConfigs {
		registries = append(registries, &model.Registry{
			Address:  key,
			Username: auth.Username,
			Password: auth.Password,
			ReadOnly: true,
		})
	}

	return registries, nil
}

func (f *filesystem) GlobalRegistryFind(addr string) (*model.Registry, error) {
	registries, err := f.GlobalRegistryList(&model.ListOptions{All: true})
	if err != nil {
		return nil, err
	}

	for _, reg := range registries {
		if reg.Address == addr {
			return reg, nil
		}
	}

	return nil, store_types.ErrRecordNotExist
}

func (f *filesystem) GlobalRegistryList(p *model.ListOptions) ([]*model.Registry, error) {
	regs, err := parseDockerConfig(f.path)
	if err != nil {
		return nil, err
	}
	return model.ApplyPagination(p, regs), nil
}

// decodeAuth decodes a base64 encoded string and returns username and password.
func decodeAuth(authStr string) (string, string, error) {
	if authStr == "" {
		return "", "", nil
	}

	decLen := base64.StdEncoding.DecodedLen(len(authStr))
	decoded := make([]byte, decLen)
	authByte := []byte(authStr)
	n, err := base64.StdEncoding.Decode(decoded, authByte)
	if err != nil {
		return "", "", err
	}
	if n > decLen {
		return "", "", fmt.Errorf("something went wrong decoding auth config")
	}
	before, after, _ := strings.Cut(string(decoded), ":")
	if before == "" || after == "" {
		return "", "", fmt.Errorf("invalid auth configuration file")
	}
	password := strings.Trim(after, "\x00")
	return before, password, nil
}
