package proxy

import (
	"fmt"
	"io"
	"net/url"
	"testing"
	"time"

	"github.com/docker/docker-credential-helpers/client"
	credspkg "github.com/docker/docker-credential-helpers/credentials"
)

type testHelper struct {
	username string
	secret   string
	err      error
}

func (h *testHelper) Output() ([]byte, error) {
	return fmt.Appendf(nil, `{"Username":%q,"Secret":%q}`, h.username, h.secret), h.err
}

func (h *testHelper) Input(in io.Reader) {
}

var _ client.Program = (*testHelper)(nil)

func TestExecAuth(t *testing.T) {
	ptrDuration := func(t time.Duration) *time.Duration { return &t }

	for _, tc := range []struct {
		name         string
		helper       client.ProgramFunc
		lifetime     *time.Duration
		currCreds    *credspkg.Credentials
		currExpiry   time.Time
		wantUsername string
		wantPassword string
		wantExpiry   time.Time
	}{{
		name: "first auth without lifetime",
		helper: func(...string) client.Program {
			return &testHelper{
				username: "user",
				secret:   "nextpass",
			}
		},
		wantUsername: "user",
		wantPassword: "nextpass",
	}, {
		name: "first auth with zero lifetime",
		helper: func(...string) client.Program {
			return &testHelper{
				username: "user",
				secret:   "nextpass",
			}
		},
		lifetime:     ptrDuration(0),
		wantUsername: "user",
		wantPassword: "nextpass",
	}, {
		name: "first auth with lifetime",
		helper: func(...string) client.Program {
			return &testHelper{
				username: "user",
				secret:   "nextpass",
			}
		},
		lifetime:     ptrDuration(time.Hour),
		wantUsername: "user",
		wantPassword: "nextpass",
		wantExpiry:   time.Now().Add(time.Hour),
	}, {
		name: "re-auth without lifetime",
		helper: func(...string) client.Program {
			return &testHelper{
				username: "user",
				secret:   "nextpass",
			}
		},
		currCreds: &credspkg.Credentials{
			Username: "user",
			Secret:   "currpass",
		},
		wantUsername: "user",
		wantPassword: "currpass",
	}, {
		name: "re-auth with zero lifetime",
		helper: func(...string) client.Program {
			return &testHelper{
				username: "user",
				secret:   "nextpass",
			}
		},
		lifetime: ptrDuration(0),
		currCreds: &credspkg.Credentials{
			Username: "user",
			Secret:   "currpass",
		},
		wantUsername: "user",
		wantPassword: "nextpass",
	}, {
		name: "re-auth when not expired",
		helper: func(...string) client.Program {
			return &testHelper{
				username: "user",
				secret:   "nextpass",
			}
		},
		lifetime: ptrDuration(time.Hour),
		currCreds: &credspkg.Credentials{
			Username: "user",
			Secret:   "currpass",
		},
		currExpiry:   time.Now().Add(time.Minute),
		wantUsername: "user",
		wantPassword: "currpass",
		wantExpiry:   time.Now().Add(time.Minute),
	}, {
		name: "re-auth when expired",
		helper: func(...string) client.Program {
			return &testHelper{
				username: "user",
				secret:   "nextpass",
			}
		},
		lifetime: ptrDuration(time.Hour),
		currCreds: &credspkg.Credentials{
			Username: "user",
			Secret:   "currpass",
		},
		currExpiry:   time.Now().Add(-1),
		wantUsername: "user",
		wantPassword: "nextpass",
		wantExpiry:   time.Now().Add(time.Hour),
	}, {
		name: "exec error",
		helper: func(...string) client.Program {
			return &testHelper{
				err: fmt.Errorf("exec error"),
			}
		},
		lifetime: ptrDuration(time.Hour),
		currCreds: &credspkg.Credentials{
			Username: "user",
			Secret:   "currpass",
		},
		currExpiry:   time.Now().Add(-1),
		wantUsername: "",
		wantPassword: "",
		wantExpiry:   time.Now().Add(-1),
	}} {
		t.Run(tc.name, func(t *testing.T) {
			cs := &execCredentials{
				helper:   tc.helper,
				lifetime: tc.lifetime,
				creds:    tc.currCreds,
				expiry:   tc.currExpiry,
			}
			url := &url.URL{
				Scheme: "https",
				Host:   "example.com",
			}
			user, pass := cs.Basic(url)
			if user != tc.wantUsername || pass != tc.wantPassword {
				t.Errorf("execCredentials.Basic(%q) = (%q, %q), want (%q, %q)", url, user, pass, tc.wantUsername, tc.wantPassword)
			}
			// All tests should finish within seconds, so the time error should be less than a minute.
			if cs.expiry.Sub(tc.wantExpiry).Abs() > time.Minute {
				t.Errorf("execCredentials.expiry = %v, want %v", cs.expiry, tc.wantExpiry)
			}
		})
	}
}
