package registry

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"net/url"
	"os"
	"testing"

	"github.com/Sirupsen/logrus"
	_ "github.com/docker/docker-registry/storagedriver/inmemory"

	"github.com/gorilla/handlers"

	"github.com/docker/docker-registry/common/testutil"
	"github.com/docker/docker-registry/configuration"
	"github.com/docker/docker-registry/digest"
)

// TestLayerAPI conducts a full of the of the layer api.
func TestLayerAPI(t *testing.T) {
	// TODO(stevvooe): This test code is complete junk but it should cover the
	// complete flow. This must be broken down and checked against the
	// specification *before* we submit the final to docker core.

	config := configuration.Configuration{
		Storage: configuration.Storage{
			"inmemory": configuration.Parameters{},
		},
	}

	app := NewApp(config)
	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
	router := v2APIRouter()

	u, err := url.Parse(server.URL)
	if err != nil {
		t.Fatalf("error parsing server url: %v", err)
	}

	imageName := "foo/bar"
	// "build" our layer file
	layerFile, tarSumStr, err := testutil.CreateRandomTarFile()
	if err != nil {
		t.Fatalf("error creating random layer file: %v", err)
	}

	layerDigest := digest.Digest(tarSumStr)

	// -----------------------------------
	// Test fetch for non-existent content
	r, err := router.GetRoute(routeNameBlob).Host(u.Host).
		URL("name", imageName,
		"digest", tarSumStr)

	resp, err := http.Get(r.String())
	if err != nil {
		t.Fatalf("unexpected error fetching non-existent layer: %v", err)
	}

	switch resp.StatusCode {
	case http.StatusNotFound:
		break // expected
	default:
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			t.Fatalf("unexpected status fetching non-existent layer: %v, %v", resp.StatusCode, resp.Status)
		}

		t.Logf("response:\n%s", string(d))
		t.Fatalf("unexpected status fetching non-existent layer: %v, %v", resp.StatusCode, resp.Status)
	}

	// ------------------------------------------
	// Test head request for non-existent content
	resp, err = http.Head(r.String())
	if err != nil {
		t.Fatalf("unexpected error checking head on non-existent layer: %v", err)
	}

	switch resp.StatusCode {
	case http.StatusNotFound:
		break // expected
	default:
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			t.Fatalf("unexpected status checking head on non-existent layer: %v, %v", resp.StatusCode, resp.Status)
		}

		t.Logf("response:\n%s", string(d))
		t.Fatalf("unexpected status checking head on non-existent layer: %v, %v", resp.StatusCode, resp.Status)
	}

	// ------------------------------------------
	// Upload a layer
	r, err = router.GetRoute(routeNameBlobUpload).Host(u.Host).
		URL("name", imageName)
	if err != nil {
		t.Fatalf("error starting layer upload: %v", err)
	}

	resp, err = http.Post(r.String(), "", nil)
	if err != nil {
		t.Fatalf("error starting layer upload: %v", err)
	}

	if resp.StatusCode != http.StatusAccepted {
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			t.Fatalf("unexpected status starting layer upload: %v, %v", resp.StatusCode, resp.Status)
		}

		t.Logf("response:\n%s", string(d))
		t.Fatalf("unexpected status starting layer upload: %v, %v", resp.StatusCode, resp.Status)
	}

	if resp.Header.Get("Location") == "" { // TODO(stevvooe): Need better check here.
		t.Fatalf("unexpected Location: %q != %q", resp.Header.Get("Location"), "foo")
	}

	if resp.Header.Get("Content-Length") != "0" {
		t.Fatalf("unexpected content-length: %q != %q", resp.Header.Get("Content-Length"), "0")
	}

	layerLength, _ := layerFile.Seek(0, os.SEEK_END)
	layerFile.Seek(0, os.SEEK_SET)

	uploadURLStr := resp.Header.Get("Location")

	// TODO(sday): Cancel the layer upload here and restart.

	query := url.Values{
		"digest": []string{layerDigest.String()},
		"length": []string{fmt.Sprint(layerLength)},
	}

	uploadURL, err := url.Parse(uploadURLStr)
	if err != nil {
		t.Fatalf("unexpected error parsing url: %v", err)
	}

	uploadURL.RawQuery = query.Encode()

	// Just do a monolithic upload
	req, err := http.NewRequest("PUT", uploadURL.String(), layerFile)
	if err != nil {
		t.Fatalf("unexpected error creating new request: %v", err)
	}

	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("unexpected error doing put: %v", err)
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusCreated:
		break // expected
	default:
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			t.Fatalf("unexpected status putting chunk: %v, %v", resp.StatusCode, resp.Status)
		}

		t.Logf("response:\n%s", string(d))
		t.Fatalf("unexpected status putting chunk: %v, %v", resp.StatusCode, resp.Status)
	}

	if resp.Header.Get("Location") == "" {
		t.Fatalf("unexpected Location: %q", resp.Header.Get("Location"))
	}

	if resp.Header.Get("Content-Length") != "0" {
		t.Fatalf("unexpected content-length: %q != %q", resp.Header.Get("Content-Length"), "0")
	}

	layerURL := resp.Header.Get("Location")

	// ------------------------
	// Use a head request to see if the layer exists.
	resp, err = http.Head(layerURL)
	if err != nil {
		t.Fatalf("unexpected error checking head on non-existent layer: %v", err)
	}

	switch resp.StatusCode {
	case http.StatusOK:
		break // expected
	default:
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			t.Fatalf("unexpected status checking head on layer: %v, %v", resp.StatusCode, resp.Status)
		}

		t.Logf("response:\n%s", string(d))
		t.Fatalf("unexpected status checking head on layer: %v, %v", resp.StatusCode, resp.Status)
	}

	logrus.Infof("fetch the layer")
	// ----------------
	// Fetch the layer!
	resp, err = http.Get(layerURL)
	if err != nil {
		t.Fatalf("unexpected error fetching layer: %v", err)
	}

	switch resp.StatusCode {
	case http.StatusOK:
		break // expected
	default:
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			t.Fatalf("unexpected status fetching layer: %v, %v", resp.StatusCode, resp.Status)
		}

		t.Logf("response:\n%s", string(d))
		t.Fatalf("unexpected status fetching layer: %v, %v", resp.StatusCode, resp.Status)
	}

	// Verify the body
	verifier := digest.NewDigestVerifier(layerDigest)
	io.Copy(verifier, resp.Body)

	if !verifier.Verified() {
		d, err := httputil.DumpResponse(resp, true)
		if err != nil {
			t.Fatalf("unexpected status checking head on layer ayo!: %v, %v", resp.StatusCode, resp.Status)
		}

		t.Logf("response:\n%s", string(d))
		t.Fatalf("response body did not pass verification")
	}
}