//go:build !unit && !gazelle
// +build !unit,!gazelle

// Copyright 2016 Google, Inc.
//
// 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 test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
	"time"

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

const (
	gcrRegistry     = "us.gcr.io"
	gcrAccessToken  = "gcr.access.token"
	gcrRefreshToken = "gcr.refresh.token"
)

func writeValidGCRCreds(accessToken, refreshToken string) error {
	oneHourFromNow := time.Now().Add(time.Hour)
	expiryJSON, err := oneHourFromNow.MarshalJSON()
	if err != nil {
		return err
	}
	credsJSON := fmt.Sprintf(`{"gcrCreds":{"access_token":"%s","refresh_token":"%s","token_expiry":%s}}`, accessToken, refreshToken, expiryJSON)
	return setTestCredentialFileContents(credsJSON)
}

func createTestCredentialFile() (*os.File, error) {
	credStorePath, err := testCredStorePath()
	if err != nil {
		return nil, err
	}

	// create the credential path, if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(credStorePath), 0777); err != nil {
		return nil, err
	}

	// create the credential file, or truncate (clear) it if it exists
	f, err := os.Create(credStorePath)
	if err != nil {
		return nil, fmt.Errorf("failed to create test credential file: %v", err)
	}
	return f, nil
}

func setTestCredentialFileContents(contents string) error {
	f, err := createTestCredentialFile()
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.WriteString(f, contents)
	return err
}

func TestEndToEnd_GCRCreds(t *testing.T) {
	err := initTestEnvironment()
	if err != nil {
		t.Fatalf("Could not initialize test environment: %v", err)
	}
	// Sanity test to verify that the environment is set up correctly.
	assertTestEnv(t)

	// Configure the helper to only use the private credential store.
	helper := helperCmd([]string{"config", "--token-source=store"})
	if err := helper.Run(); err != nil {
		t.Fatalf("Failed to configure the helper: %v", err)
	}
	// Verify the contents of the config.
	configPath, err := testConfigPath()
	if err != nil {
		t.Fatalf("Unable construct test config path: %v", err)
	}
	configBuf, err := ioutil.ReadFile(configPath)
	if err != nil {
		t.Fatalf("Unable to verify config: %v", err)
	} else if configStr := string(configBuf); strings.TrimSpace(configStr) != `{"TokenSources":["store"]}` {
		t.Fatalf("Expected config: %s, was: %s", `{"TokenSources":["store"]}`, configStr)
	}

	if err := writeValidGCRCreds(gcrAccessToken, gcrRefreshToken); err != nil {
		t.Fatalf("Unable to write creds store: %#v", err)
	}

	// retrieve the stored credentials
	helper = helperCmd([]string{"get"})
	var out bytes.Buffer
	helper.Stdout = &out
	helper.Stdin = strings.NewReader(gcrRegistry)
	if err = helper.Run(); err != nil {
		t.Fatalf("`get` failed: %v, Stdout: %s", err, string(out.Bytes()))
	}

	// Verify the credentials
	var creds credentials.Credentials
	if err := json.NewDecoder(bytes.NewReader(out.Bytes())).Decode(&creds); err != nil {
		t.Fatalf("Unable to decode credentials returned from get: %v", err)
	}

	if match, err := regexp.MatchString("_dcgcr_(?:[0-9]+_)*token", creds.Username); !match || err != nil {
		// Fail if not a dev version.
		devUsername := "_dcgcr_0_0_0_token"
		if creds.Username != devUsername {
			t.Errorf("Bad GCR username: Wanted: %q, Got (val, err): %q, %v", "_dcgcr_(?:[0-9]+_)*token", creds.Username, err)
		}
	}
	if creds.Secret != gcrAccessToken {
		t.Errorf("Bad GCR access token. Wanted: %s, Got: %s", gcrAccessToken, creds.Secret)
	}

	// erase the credentials
	helper = helperCmd([]string{"erase"})
	helper.Stdin = strings.NewReader(gcrRegistry)
	if err = helper.Run(); err == nil {
		t.Fatal("Expected erase to fail for GCR hostname.")
	}
}
