package cmd

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	golog "github.com/ipfs/go-log"
	ma "github.com/multiformats/go-multiaddr"
	manet "github.com/multiformats/go-multiaddr-net"
	"github.com/qri-io/dataset"
	"github.com/qri-io/qfs"
	"github.com/qri-io/qri/config"
	repotest "github.com/qri-io/qri/repo/test"
)

func init() {
	// TODO (b5) - ask go-ipfs folks if the shutdown messages can be INFO level
	// instead of error level to avoid:
	// 10:12:42.396 ERROR       core: core is shutting down...
	// after all sorts of tests
	golog.SetLogLevel("core", "CRITICAL")
}

// ioReset resets the in, out, errs buffers
// convenience function used in testing
func ioReset(in, out, errs *bytes.Buffer) {
	in.Reset()
	out.Reset()
	errs.Reset()
}

func confirmQriNotRunning() error {
	addr, err := ma.NewMultiaddr(config.DefaultAPIAddress)
	if err != nil {
		return fmt.Errorf(err.Error())
	}
	l, err := manet.Listen(addr)
	if err != nil {
		return fmt.Errorf("it looks like a qri server is already running on address %s, please close before running tests", config.DefaultAPIAddress)
	}

	l.Close()
	return nil
}

const moviesCSVData = `movie_title,duration
Avatar,178
Pirates of the Caribbean: At World's End,169
Spectre,148
The Dark Knight Rises ,164
Star Wars: Episode VII - The Force Awakens,15
John Carter,132
Spider-Man 3,156
Tangled,100
Avengers: Age of Ultron,141`

const moviesCSVData2 = `movie_title,duration
Avatar,178
Pirates of the Caribbean: At World's End,169
Spectre,148
The Dark Knight Rises ,164
Star Wars: Episode VII - The Force Awakens,15
John Carter,132
Spider-Man 3,156
Tangled,100
Avengers: Age of Ultron,141
A Wild Film Appears!,2000
Another Film!,121`

const linksJSONData = `[
  "http://datatogether.org",
  "https://datatogether.org/css/style.css",
  "https://datatogether.org/img/favicon.ico",
  "https://datatogether.org",
  "https://datatogether.org/public-record",
  "https://datatogether.org/activities",
  "https://datatogether.org/activities/harvesting",
  "https://datatogether.org/activities/monitoring",
  "https://datatogether.org/activities/storing",
  "https://datatogether.org/activities/rescuing",
  "http://2017.code4lib.org",
  "https://datatogether.org/presentations/Code4Lib%202017%20-%20Golden%20Age%20for%20Libraries%20-%20Storing%20Data%20Together.pdf",
  "https://datatogether.org/presentations/Code4Lib%202017%20-%20Golden%20Age%20for%20Libraries%20-%20Storing%20Data%20Together.key",
  "http://www.esipfed.org/meetings/upcoming-meetings/esip-summer-meeting-2017",
  "https://datatogether.org/presentations/Data%20Together%20-%20ESIP%20Summer%20Meeting%20July%202017.pdf",
  "https://datatogether.org/presentations/Data%20Together%20-%20ESIP%20Summer%20Meeting%20July%202017.key",
  "https://archive.org/details/ndsr-dc-2017",
  "https://datatogether.org/presentations/Data%20Together%20-%20NDSR%20-%20swadeshi.pdf",
  "https://datatogether.org/presentations/Data%20Together%20-%20NDSR%20-%20swadeshi.key",
  "https://github.com/datatogether"
]`

const profileData = `
{
	"description" : "I'm a description!"
}
`

// Test that saving a dataset with a relative body path works, and validate the contents of that
// body match what was given to the save command.
func TestSaveRelativeBodyPath(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_relative_body", "qri_test_save_relative_body")
	defer run.Delete()

	// Save a dataset which has a body as a relative path
	run.MustExec(t, "qri save --file=testdata/movies/ds_ten.yaml me/test_movies")

	// Read body from the dataset that was saved.
	dsPath := run.GetPathForDataset(t, 0)
	actualBody := run.ReadBodyFromIPFS(t, dsPath+"/body.csv")

	// Read the body from the testdata input file.
	f, _ := os.Open("testdata/movies/body_ten.csv")
	expectBytes, _ := ioutil.ReadAll(f)
	expectBody := string(expectBytes)

	// Make sure they match.
	if actualBody != expectBody {
		t.Errorf("error reading body, expect \"%s\", actual \"%s\"", actualBody, expectBody)
	}
}

// Test that saving three revisions, then removing the newest two, leaves the first body.
func TestRemoveOnlyTwoRevisions(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_remove_only_two_revisions", "qri_test_remove_only_two_revisions")
	defer run.Delete()

	// Save three revisions, then remove two
	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/test_movies")
	run.MustExec(t, "qri save --body=testdata/movies/body_twenty.csv me/test_movies")
	run.MustExec(t, "qri save --body=testdata/movies/body_thirty.csv me/test_movies")
	run.MustExec(t, "qri remove me/test_movies --revisions=2")

	// Read body from the dataset that was saved.
	dsPath := run.GetPathForDataset(t, 0)
	actualBody := run.ReadBodyFromIPFS(t, dsPath+"/body.csv")

	// Read the body from the testdata input file.
	f, _ := os.Open("testdata/movies/body_ten.csv")
	expectBytes, _ := ioutil.ReadAll(f)
	expectBody := string(expectBytes)

	// Make sure they match.
	if expectBody != actualBody {
		t.Errorf("error reading body, expect \"%s\", actual \"%s\"", expectBody, actualBody)
	}
}

// Test that adding three revision, then removing all of them leaves nothing.
func TestRemoveAllRevisionsLongForm(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_remove_all_revisions_long_form", "qri_test_remove_all_revisions_long_form")
	defer run.Delete()

	// Save three versions, then remove all of them.
	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/test_movies")
	run.MustExec(t, "qri save --body=testdata/movies/body_twenty.csv me/test_movies")
	run.MustExec(t, "qri save --body=testdata/movies/body_thirty.csv me/test_movies")
	run.MustExec(t, "qri remove me/test_movies --revisions=all")

	// Read path for dataset, which shouldn't exist anymore.
	dsPath := run.GetPathForDataset(t, 0)
	if dsPath != "" {
		t.Errorf("expected dataset to be removed entirely, found at \"%s\"", dsPath)
	}
}

// Test that adding three revision, then removing all of them leaves nothing, using --all.
func TestRemoveAllRevisionsShortForm(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_remove_all_revisions_short_form", "qri_test_remove_all_revisions_short_form")
	defer run.Delete()

	// Save three versions, then remove all of them, using the --all flag.
	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/test_movies")
	run.MustExec(t, "qri save --body=testdata/movies/body_twenty.csv me/test_movies")
	run.MustExec(t, "qri save --body=testdata/movies/body_thirty.csv me/test_movies")
	run.MustExec(t, "qri remove me/test_movies --all")

	// Read path for dataset, which shouldn't exist anymore.
	dsPath := run.GetPathForDataset(t, 0)
	if dsPath != "" {
		t.Errorf("expected dataset to be removed entirely, found at \"%s\"", dsPath)
	}
}

// Test that save can override a single component, meta in this case.
func TestSaveThenOverrideMetaComponent(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_then_override_meta", "qri_test_save_then_override_meta")
	defer run.Delete()

	// Save a version, then save another with a new meta component.
	run.MustExec(t, "qri save --file=testdata/movies/ds_ten.yaml me/test_ds")
	run.MustExec(t, "qri save --file=testdata/movies/meta_override.yaml me/test_ds")

	// Read head from the dataset that was saved, as json string.
	dsPath := run.GetPathForDataset(t, 0)
	actual := run.DatasetMarshalJSON(t, dsPath)

	// This dataset is ds_ten.yaml, with the meta replaced by meta_override.yaml.
	expect := `{"bodyPath":"/ipfs/QmXhsUK6vGZrqarhw9Z8RCXqhmEpvtVByKtaYVarbDZ5zn","commit":{"author":{"id":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B"},"message":"meta:\n\tupdated title","path":"/ipfs/QmeYjmGoCVkrkn95KWq6fcs4Y5JcHcKHvSSkXTskidoNod","qri":"cm:0","signature":"njCFxpGqq0xJSrjgxC289KncjflqA0e00txweEqIyUTvEKSUBKHcfQmx4OQIJzJqQJdcjIEzFrwP9cdquozRgsnrpsSfKb+wBWdtbnrg8zfat0X/Dqjro6JD7afJf0gU9s5SDi/s8g/qZOLwWh1nuoH4UAeUX+l3DH0ocFjeD6r/YkMJ0KXaWaFloKP8UPasfqoei9PxxmYQuAnFMqpXFisB7mKFAbgbpF3eL80UcbQPTih7WF11SBym/AzJhGNvOivOjmRxKGEuqEH9g3NPTEQr+LnP415X4qiaZA6MVmOO66vC0diUN4vJUMvhTsWnVEBtgqjTRYlSaYwabHv/gA==","timestamp":"2001-01-01T01:02:01.000000001Z","title":"meta updated title"},"meta":{"qri":"md:0","title":"different title"},"path":"/ipfs/QmUMY4vWqaybWuxatoMx8Rk1SCHHLtrKZGTTje556a1sYt","previousPath":"/ipfs/QmWqZYVT4RU99Q191PySiXoeN8Ft5M8s8zLP4N4dNxtKxT","qri":"ds:0","structure":{"checksum":"QmcXDEGeWdyzfFRYyPsQVab5qszZfKqxTMEoXRDSZMyrhf","depth":2,"errCount":1,"entries":8,"format":"csv","formatConfig":{"headerRow":true,"lazyQuotes":true},"length":224,"qri":"st:0","schema":{"items":{"items":[{"title":"movie_title","type":"string"},{"title":"duration","type":"integer"}],"type":"array"},"type":"array"}}}`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("dataset (-want +got):\n%s", diff)
	}
}

// Test save with a body, then adding a meta
func TestSaveWithBodyThenAddMetaComponent(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_with_body_then_override_meta", "qri_test_save_with_body_then_override_meta")
	defer run.Delete()

	// Save a version with a csv body, then another with a new meta component.
	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/simple_ds")
	run.MustExec(t, "qri save --file=testdata/movies/meta_override.yaml me/simple_ds")

	// Read head from the dataset that was saved, as json string.
	dsPath := run.GetPathForDataset(t, 0)
	actual := run.DatasetMarshalJSON(t, dsPath)

	// This version has a commit message about the meta being added
	expect := `{"bodyPath":"/ipfs/QmXhsUK6vGZrqarhw9Z8RCXqhmEpvtVByKtaYVarbDZ5zn","commit":{"author":{"id":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B"},"message":"meta added","path":"/ipfs/QmVieZLxyvYRPyGPt6MKDwTMoNb6EfqJshg4kcVmrxfJEZ","qri":"cm:0","signature":"njCFxpGqq0xJSrjgxC289KncjflqA0e00txweEqIyUTvEKSUBKHcfQmx4OQIJzJqQJdcjIEzFrwP9cdquozRgsnrpsSfKb+wBWdtbnrg8zfat0X/Dqjro6JD7afJf0gU9s5SDi/s8g/qZOLwWh1nuoH4UAeUX+l3DH0ocFjeD6r/YkMJ0KXaWaFloKP8UPasfqoei9PxxmYQuAnFMqpXFisB7mKFAbgbpF3eL80UcbQPTih7WF11SBym/AzJhGNvOivOjmRxKGEuqEH9g3NPTEQr+LnP415X4qiaZA6MVmOO66vC0diUN4vJUMvhTsWnVEBtgqjTRYlSaYwabHv/gA==","timestamp":"2001-01-01T01:02:01.000000001Z","title":"meta added"},"meta":{"qri":"md:0","title":"different title"},"path":"/ipfs/QmSbKpYbjdro3kLWPLFXQ3pE3g5JY1mHkRonG2MmPJ2wm7","previousPath":"/ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF","qri":"ds:0","structure":{"checksum":"QmcXDEGeWdyzfFRYyPsQVab5qszZfKqxTMEoXRDSZMyrhf","depth":2,"errCount":1,"entries":8,"format":"csv","formatConfig":{"headerRow":true,"lazyQuotes":true},"length":224,"qri":"st:0","schema":{"items":{"items":[{"title":"movie_title","type":"string"},{"title":"duration","type":"integer"}],"type":"array"},"type":"array"}}}`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("dataset (-want +got):\n%s", diff)
	}
}

// Test save with a body, then adding a meta
func TestSaveWithBodyThenAddMetaAndSmallBodyChange(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_then_override_meta_and_body", "qri_test_save_then_override_meta_and_body")
	defer run.Delete()

	// Save a version with a csv body, then another with a new meta component and different body.
	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/simple_ds")
	run.MustExec(t, "qri save --body=testdata/movies/body_twenty.csv --file=testdata/movies/meta_override.yaml me/simple_ds")

	// Read head from the dataset that was saved, as json string.
	dsPath := run.GetPathForDataset(t, 0)
	actual := run.DatasetMarshalJSON(t, dsPath)

	// This version has a commit message about the meta being added and body changing
	expect := `{"bodyPath":"/ipfs/QmeLmPMNSCxVxCdDmdunBCfiN1crb3C2eUnZex6QgHpFiB","commit":{"author":{"id":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B"},"message":"meta added\nbody:\n\tchanged by 54%","path":"/ipfs/QmWjDi8FrAjtSt3drCR4Vh6FDvCbKrN24sM5vtLyEJu8VJ","qri":"cm:0","signature":"eLr+Pk3wg8JSoeARoelNzdKLeiYFqL1k87YpXtSXigO39cAXFHg8FQki/+zt+gLPCBmPfD/mfQbR3R2mQmxyR4F+wQ1wbwkzJfumWbluyPfBsKbPQ55XLhkYUg6Ho5HqVsBn7sZ28WJ/1+GPC5xMaFYeMLTRnD2jflB5NR33eYxVQL/IDUybhgmlV5D2GOeZAOjQcCtgf0Me8o/HMqn9TsW9mNzilf7GF7lxyV+Jrz1pYjvOPlXoJGqRGRBnDbIzvEwmH7XBGHZvsbCntFw1XEEJIQslTm5mFbVrGTzqvmsPIB/SfbTjWL4Elq4uAocr7Mnu9TFKD15XNaZ8pMhaQw==","timestamp":"2001-01-01T01:02:01.000000001Z","title":"updated meta and body"},"meta":{"qri":"md:0","title":"different title"},"path":"/ipfs/QmZhwebAc3Jh8uz6mRcpAFzaxZun5JidoprqJgdhp95Djd","previousPath":"/ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF","qri":"ds:0","structure":{"checksum":"QmSa4i985cF3dxNHxD5mSN7c6q1eYa83uNo1pLRmPZgTsa","depth":2,"errCount":1,"entries":18,"format":"csv","formatConfig":{"headerRow":true,"lazyQuotes":true},"length":532,"qri":"st:0","schema":{"items":{"items":[{"title":"movie_title","type":"string"},{"title":"duration","type":"integer"}],"type":"array"},"type":"array"}}}`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("dataset (-want +got):\n%s", diff)
	}
}

// Test that saving with two components at once will merge them together.
func TestSaveTwoComponents(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_two_components", "qri_test_save_two_component")
	defer run.Delete()

	// Save a version, then same another with two components at once
	run.MustExec(t, "qri save --file=testdata/movies/ds_ten.yaml me/test_ds")
	run.MustExec(t, "qri save --file=testdata/movies/meta_override.yaml --file=testdata/movies/structure_override.json me/test_ds")

	// Read head from the dataset that was saved, as json string.
	dsPath := run.GetPathForDataset(t, 0)
	actual := run.DatasetMarshalJSON(t, dsPath)

	// This dataset is ds_ten.yaml, with the meta replaced by meta_override ("different title") and
	// the structure replaced by structure_override (lazyQuotes: false && title: "name").
	expect := `{"bodyPath":"/ipfs/QmXhsUK6vGZrqarhw9Z8RCXqhmEpvtVByKtaYVarbDZ5zn","commit":{"author":{"id":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B"},"message":"meta:\n\tupdated title\nstructure:\n\tupdated formatConfig.lazyQuotes\n\tupdated schema.items.items.0.title","path":"/ipfs/Qmf51CD3zW64ffoWja32bKh3BSyMwvMSbh9A8PtrA7fDJi","qri":"cm:0","signature":"njCFxpGqq0xJSrjgxC289KncjflqA0e00txweEqIyUTvEKSUBKHcfQmx4OQIJzJqQJdcjIEzFrwP9cdquozRgsnrpsSfKb+wBWdtbnrg8zfat0X/Dqjro6JD7afJf0gU9s5SDi/s8g/qZOLwWh1nuoH4UAeUX+l3DH0ocFjeD6r/YkMJ0KXaWaFloKP8UPasfqoei9PxxmYQuAnFMqpXFisB7mKFAbgbpF3eL80UcbQPTih7WF11SBym/AzJhGNvOivOjmRxKGEuqEH9g3NPTEQr+LnP415X4qiaZA6MVmOO66vC0diUN4vJUMvhTsWnVEBtgqjTRYlSaYwabHv/gA==","timestamp":"2001-01-01T01:02:01.000000001Z","title":"updated meta and structure"},"meta":{"qri":"md:0","title":"different title"},"path":"/ipfs/QmfMHjCcDuPkacYYEfVwURyRnCuCqjaRYwtMoydYzbwEuy","previousPath":"/ipfs/QmWqZYVT4RU99Q191PySiXoeN8Ft5M8s8zLP4N4dNxtKxT","qri":"ds:0","structure":{"checksum":"QmcXDEGeWdyzfFRYyPsQVab5qszZfKqxTMEoXRDSZMyrhf","depth":2,"errCount":1,"entries":8,"format":"csv","formatConfig":{"headerRow":true,"lazyQuotes":false},"length":224,"qri":"st:0","schema":{"items":{"items":[{"title":"name","type":"string"},{"title":"duration","type":"integer"}]},"type":"array"}}}`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("dataset (-want +got):\n%s", diff)
	}
}

// Test that save can override just the transform
func TestSaveThenOverrideTransform(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_file_transform", "qri_test_save_file_transform")
	defer run.Delete()

	// Save a version, then save another with a transform
	run.MustExec(t, "qri save --file=testdata/movies/ds_ten.yaml me/test_ds")
	run.MustExec(t, "qri save --file=testdata/movies/tf.star me/test_ds")

	// Read head from the dataset that was saved, as json string.
	dsPath := run.GetPathForDataset(t, 0)
	actual := run.DatasetMarshalJSON(t, dsPath)

	// This dataset is ds_ten.yaml, with an added transform section
	expect := `{"bodyPath":"/ipfs/QmXhsUK6vGZrqarhw9Z8RCXqhmEpvtVByKtaYVarbDZ5zn","commit":{"author":{"id":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B"},"message":"transform added","path":"/ipfs/QmWq1C8kx6d4Fe5hBsUaqXFh2VDUMzaE5ewTNjqgGnXivV","qri":"cm:0","signature":"njCFxpGqq0xJSrjgxC289KncjflqA0e00txweEqIyUTvEKSUBKHcfQmx4OQIJzJqQJdcjIEzFrwP9cdquozRgsnrpsSfKb+wBWdtbnrg8zfat0X/Dqjro6JD7afJf0gU9s5SDi/s8g/qZOLwWh1nuoH4UAeUX+l3DH0ocFjeD6r/YkMJ0KXaWaFloKP8UPasfqoei9PxxmYQuAnFMqpXFisB7mKFAbgbpF3eL80UcbQPTih7WF11SBym/AzJhGNvOivOjmRxKGEuqEH9g3NPTEQr+LnP415X4qiaZA6MVmOO66vC0diUN4vJUMvhTsWnVEBtgqjTRYlSaYwabHv/gA==","timestamp":"2001-01-01T01:02:01.000000001Z","title":"transform added"},"meta":{"qri":"md:0","title":"example movie data"},"path":"/ipfs/QmcHnffD53hYSFHhvpBUqoD2J4uT3tdtcSqsUgqDLk19ko","previousPath":"/ipfs/QmWqZYVT4RU99Q191PySiXoeN8Ft5M8s8zLP4N4dNxtKxT","qri":"ds:0","structure":{"checksum":"QmcXDEGeWdyzfFRYyPsQVab5qszZfKqxTMEoXRDSZMyrhf","depth":2,"errCount":1,"entries":8,"format":"csv","formatConfig":{"headerRow":true,"lazyQuotes":true},"length":224,"qri":"st:0","schema":{"items":{"items":[{"title":"movie_title","type":"string"},{"title":"duration","type":"integer"}],"type":"array"},"type":"array"}},"transform":{"qri":"tf:0","scriptPath":"/ipfs/Qmb69tx5VCL7q7EfkGKpDgESBysmDbohoLvonpbgri48NN","syntax":"starlark","syntaxVersion":"test_version"}}`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("dataset (-want +got):\n%s", diff)
	}
}

// Test that save can override just the viz
func TestSaveThenOverrideViz(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_file_viz", "qri_test_save_file_viz")
	defer run.Delete()

	// Save a version, then save another with a viz template
	run.MustExec(t, "qri save --file=testdata/movies/ds_ten.yaml me/test_ds")
	run.MustExec(t, "qri save --file=testdata/template.html me/test_ds")

	// Read head from the dataset that was saved, as json string.
	dsPath := run.GetPathForDataset(t, 0)
	actual := run.DatasetMarshalJSON(t, dsPath)

	// This dataset is ds_ten.yaml, with an added viz section
	expect := `{"bodyPath":"/ipfs/QmXhsUK6vGZrqarhw9Z8RCXqhmEpvtVByKtaYVarbDZ5zn","commit":{"author":{"id":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B"},"message":"viz added","path":"/ipfs/QmfGaBvAhXK91S7aXTQhhLgrNNSJcnjZThJzBKjRasW7t2","qri":"cm:0","signature":"njCFxpGqq0xJSrjgxC289KncjflqA0e00txweEqIyUTvEKSUBKHcfQmx4OQIJzJqQJdcjIEzFrwP9cdquozRgsnrpsSfKb+wBWdtbnrg8zfat0X/Dqjro6JD7afJf0gU9s5SDi/s8g/qZOLwWh1nuoH4UAeUX+l3DH0ocFjeD6r/YkMJ0KXaWaFloKP8UPasfqoei9PxxmYQuAnFMqpXFisB7mKFAbgbpF3eL80UcbQPTih7WF11SBym/AzJhGNvOivOjmRxKGEuqEH9g3NPTEQr+LnP415X4qiaZA6MVmOO66vC0diUN4vJUMvhTsWnVEBtgqjTRYlSaYwabHv/gA==","timestamp":"2001-01-01T01:02:01.000000001Z","title":"viz added"},"meta":{"qri":"md:0","title":"example movie data"},"path":"/ipfs/QmUf8ZtDTwWj2d94biAnRSDxEnDJK4LQXaAbAXEpGdNrfC","previousPath":"/ipfs/QmWqZYVT4RU99Q191PySiXoeN8Ft5M8s8zLP4N4dNxtKxT","qri":"ds:0","structure":{"checksum":"QmcXDEGeWdyzfFRYyPsQVab5qszZfKqxTMEoXRDSZMyrhf","depth":2,"errCount":1,"entries":8,"format":"csv","formatConfig":{"headerRow":true,"lazyQuotes":true},"length":224,"qri":"st:0","schema":{"items":{"items":[{"title":"movie_title","type":"string"},{"title":"duration","type":"integer"}],"type":"array"},"type":"array"}},"viz":{"format":"html","qri":"vz:0","renderedPath":"/ipfs/QmdkMb5K7rEUDegPe3Pibi7dk3xdvBq6UFfuD5PDkLRytV","scriptPath":"/ipfs/QmRaVGip3V9fVBJheZN6FbUajD3ZLNjHhXdjrmfg2JPoo5"}}`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("dataset (-want +got):\n%s", diff)
	}
}

// Test that save can combine a meta compoent, and a transform, and a viz
func TestSaveThenOverrideMetaAndTransformAndViz(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_then_override_meta_transform_viz", "qri_test_save_then_override_meta_transfrom_viz")
	defer run.Delete()

	// Save a version, then save another with three components at once
	run.MustExec(t, "qri save --file=testdata/movies/ds_ten.yaml me/test_ds")
	run.MustExec(t, "qri save --file=testdata/movies/meta_override.yaml --file=testdata/movies/tf.star --file=testdata/template.html me/test_ds")

	// Read head from the dataset that was saved, as json string.
	dsPath := run.GetPathForDataset(t, 0)
	actual := run.DatasetMarshalJSON(t, dsPath)

	// This dataset is ds_ten.yaml, with an added meta component, and transform, and viz
	expect := `{"bodyPath":"/ipfs/QmXhsUK6vGZrqarhw9Z8RCXqhmEpvtVByKtaYVarbDZ5zn","commit":{"author":{"id":"QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B"},"message":"meta:\n\tupdated title\nviz added\ntransform added","path":"/ipfs/QmW66PYuz128VP4gds4HgxB7q9LKUfuZUkX3VYQY8rhk4n","qri":"cm:0","signature":"njCFxpGqq0xJSrjgxC289KncjflqA0e00txweEqIyUTvEKSUBKHcfQmx4OQIJzJqQJdcjIEzFrwP9cdquozRgsnrpsSfKb+wBWdtbnrg8zfat0X/Dqjro6JD7afJf0gU9s5SDi/s8g/qZOLwWh1nuoH4UAeUX+l3DH0ocFjeD6r/YkMJ0KXaWaFloKP8UPasfqoei9PxxmYQuAnFMqpXFisB7mKFAbgbpF3eL80UcbQPTih7WF11SBym/AzJhGNvOivOjmRxKGEuqEH9g3NPTEQr+LnP415X4qiaZA6MVmOO66vC0diUN4vJUMvhTsWnVEBtgqjTRYlSaYwabHv/gA==","timestamp":"2001-01-01T01:02:01.000000001Z","title":"updated meta, viz, and transform"},"meta":{"qri":"md:0","title":"different title"},"path":"/ipfs/QmYd9vnBJYS3SZzMfrGfxQdL9dckgkmc51b7rsMnZaDLnA","previousPath":"/ipfs/QmWqZYVT4RU99Q191PySiXoeN8Ft5M8s8zLP4N4dNxtKxT","qri":"ds:0","structure":{"checksum":"QmcXDEGeWdyzfFRYyPsQVab5qszZfKqxTMEoXRDSZMyrhf","depth":2,"errCount":1,"entries":8,"format":"csv","formatConfig":{"headerRow":true,"lazyQuotes":true},"length":224,"qri":"st:0","schema":{"items":{"items":[{"title":"movie_title","type":"string"},{"title":"duration","type":"integer"}],"type":"array"},"type":"array"}},"transform":{"qri":"tf:0","scriptPath":"/ipfs/Qmb69tx5VCL7q7EfkGKpDgESBysmDbohoLvonpbgri48NN","syntax":"starlark","syntaxVersion":"test_version"},"viz":{"format":"html","qri":"vz:0","renderedPath":"/ipfs/QmW3V8zbPU4wAnzv2zbCjkiTuo7NcsVmaLfFrnHJV1fpKV","scriptPath":"/ipfs/QmRaVGip3V9fVBJheZN6FbUajD3ZLNjHhXdjrmfg2JPoo5"}}`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("dataset (-want +got):\n%s", diff)
	}
}

// Test that saving a full dataset with a component at the same time is an error
func TestSaveDatasetWithComponentError(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_with_component_error", "qri_test_save_with_component_error")
	defer run.Delete()

	// Try to save with two conflicting components, but this returns an error
	err := run.ExecCommand("qri save --file=testdata/movies/ds_ten.yaml --file=testdata/movies/meta_override.yaml me/test_ds")
	if err == nil {
		t.Errorf("expected error, did not get one")
	}
	expect := `conflict, cannot save a full dataset with other components`
	if err.Error() != expect {
		t.Errorf("expected error: \"%s\", got: \"%s\"", expect, err.Error())
	}
}

// Test that saving with two components of the same kind is an error
func TestSaveConflictingComponents(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_conflicting_components", "qri_test_save_conflicting_components")
	defer run.Delete()

	// Save two versions, but second has a conflict error
	run.MustExec(t, "qri save --file=testdata/movies/ds_ten.yaml me/test_ds")
	err := run.ExecCommand("qri save --file=testdata/movies/meta_override.yaml --file=testdata/movies/meta_override.yaml me/test_ds")
	if err == nil {
		t.Errorf("expected error, did not get one")
	}
	expect := `conflict, multiple components of kind "md"`
	if err.Error() != expect {
		t.Errorf("expected error: \"%s\", got: \"%s\"", expect, err.Error())
	}
}

// Test that running a transform without any changes will not make a new commit
func TestSaveTransformWithoutChanges(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_transform_without_changes", "qri_test_transform_without_changes")
	defer run.Delete()

	// Save a version, then another with no changes
	run.MustExec(t, "qri save --file=testdata/movies/tf_123.star me/test_ds")

	errOut := run.GetCommandErrOutput()
	if !strings.Contains(errOut, "setting body") {
		t.Errorf("expected ErrOutput to contain print statement from transform script. errOutput:\n%s", errOut)
	}

	err := run.ExecCommand("qri save --file=testdata/movies/tf_123.star me/test_ds")
	expect := `error saving: no changes`
	if err == nil {
		t.Fatalf("expected error: did not get one")
	}
	if err.Error() != expect {
		t.Errorf("expected error: \"%s\", got: \"%s\"", expect, err.Error())
	}
}

// Test that calling `get_body` will retrieve the body of the previous version.
func TestTransformUsingGetBodyAndSetBody(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_save_transform_using_get_and_set_body", "qri_test_save_transform_get_and_set_body")
	defer run.Delete()

	// Save two versions, the second of which uses get_body in a transformation
	run.MustExec(t, "qri save --body=testdata/movies/body_two.json me/test_ds")
	run.MustExec(t, "qri save --file=testdata/movies/tf_add_one.star me/test_ds")

	// Read body from the dataset that was created with the transform
	dsPath := run.GetPathForDataset(t, 0)
	actualBody := run.ReadBodyFromIPFS(t, dsPath+"/body.json")

	// This body is body_two.json, with the numbers in the second column increased by 1.
	expectBody := `[["Avatar",179],["Pirates of the Caribbean: At World's End",170]]`
	if actualBody != expectBody {
		t.Errorf("error, dataset actual:\n%s\nexpect:\n%s\n", actualBody, expectBody)
	}
}

// Test that modifying a transform that produces the same body results in a new version
func TestSaveTransformModifiedButSameBody(t *testing.T) {
	if err := confirmQriNotRunning(); err != nil {
		t.Skip(err.Error())
	}

	run := NewTestRunner(t, "test_peer_transform_modified", "qri_test_transform_modified")
	defer run.Delete()

	// Save a version
	run.MustExec(t, "qri save --file=testdata/movies/tf_123.star me/test_ds")

	// Save another version with a modified transform that produces the same body
	err := run.ExecCommand("qri save --file=testdata/movies/tf_modified.star me/test_ds")

	if err != nil {
		t.Errorf("unexpected error: %q", err)
	}

	output := run.MustExec(t, "qri log me/test_ds")
	expect := `1   Commit:  /ipfs/QmXf94PJ7gumMmcsULtJVLnA2pofRD1aUSarB39d7cdvUg
    Date:    Sun Dec 31 20:02:01 EST 2000
    Storage: local
    Size:    7 B

    transform updated scriptBytes
    transform:
    	updated scriptBytes

2   Commit:  /ipfs/QmTVgjgHdRKghVL6cUTXxiyyAZWZtEsPVhmoCHCCBKcyz9
    Date:    Sun Dec 31 20:01:01 EST 2000
    Storage: local
    Size:    7 B

    created dataset from tf_123.star

`
	if diff := cmp.Diff(expect, output); diff != "" {
		t.Errorf("log (-want +got):\n%s", diff)
	}
}

// Test that save can be called with a readme file
func TestSaveReadmeFromFile(t *testing.T) {
	run := NewTestRunner(t, "test_peer_save_readme_file", "qri_test_save_readme_file")
	defer run.Delete()

	// Save two versions, one with a body, the second with a readme
	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/save_readme_file")
	run.MustExec(t, "qri save --file=testdata/movies/about_movies.md me/save_readme_file")

	// Verify we can get the readme back
	actual := run.MustExec(t, "qri get readme me/save_readme_file")
	expect := `format: md
qri: rm:0
scriptPath: /ipfs/QmQPbLdDwyAzCmKayuHGeNGx5eboDv5aXTMuw2daUuneCb

`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("readme.md contents (-want +got):\n%s", diff)
	}

	// As well as the readme script bytes
	actual = run.MustExec(t, "qri get readme.script me/save_readme_file")
	expect = `# Title

This is a dataset about movies

`
	if diff := cmp.Diff(expect, actual); diff != "" {
		t.Errorf("readme.md contents (-want +got):\n%s", diff)
	}
}

// Test that renaming a dataset after registration (which changes the username) works correctly
func TestRenameAfterRegistration(t *testing.T) {
	run := NewTestRunnerWithTempRegistry(t, "test_peer_rename_after_reg", "rename_after_reg")
	defer run.Delete()

	// Create a dataset, using the "anonymous" generated username.
	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/first_name")

	// Verify the raw references in the repo
	output := run.MustExec(t, "qri list --raw")
	expect := `0 Peername:  test_peer_rename_after_reg
  ProfileID: QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B
  Name:      first_name
  Path:      /ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF
  FSIPath:   
  Published: false

`
	if diff := cmp.Diff(expect, output); diff != "" {
		t.Errorf("unexpected (-want +got):\n%s", diff)
	}

	// Register (using a mock server) which changes the username
	err := run.ExecCommandWithStdin(run.Context, "qri registry signup --username real_peer --email me@example.com", "myPassword")
	if err != nil {
		t.Fatal(err)
	}

	output = run.MustExec(t, "qri list --raw")
	expect = `0 Peername:  real_peer
  ProfileID: QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B
  Name:      first_name
  Path:      /ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF
  FSIPath:   
  Published: false

`
	if diff := cmp.Diff(expect, output); diff != "" {
		t.Errorf("unexpected (-want +got):\n%s", diff)
	}

	// Rename the created dataset, which should work even though our username changed
	run.MustExec(t, "qri rename me/first_name me/second_name")

	output = run.MustExec(t, "qri list --raw")
	expect = `0 Peername:  real_peer
  ProfileID: QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B
  Name:      second_name
  Path:      /ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF
  FSIPath:   
  Published: false

`
	if diff := cmp.Diff(expect, output); diff != "" {
		t.Errorf("unexpected (-want +got):\n%s", diff)
	}

	// Rename a second time, make sure this works still
	run.MustExec(t, "qri rename me/second_name me/third_name")

	output = run.MustExec(t, "qri list --raw")
	expect = `0 Peername:  real_peer
  ProfileID: QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B
  Name:      third_name
  Path:      /ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF
  FSIPath:   
  Published: false

`
	if diff := cmp.Diff(expect, output); diff != "" {
		t.Errorf("unexpected (-want +got):\n%s", diff)
	}
}

// Test that list can format output as json
func TestListFormatJson(t *testing.T) {
	run := NewTestRunner(t, "test_peer_list_format_json", "list_format_json")
	defer run.Delete()

	run.MustExec(t, "qri save --body=testdata/movies/body_ten.csv me/my_ds")

	// Verify the references in json format
	output := run.MustExec(t, "qri list --format json")
	expect := `[
  {
    "username": "test_peer_list_format_json",
    "profileID": "QmeL2mdVka1eahKENjehK6tBxkkpk5dNQ1qMcgWi7Hrb4B",
    "name": "my_ds",
    "path": "/ipfs/QmNX9ZKXtdskpYSQ5spd1qvqB2CPoWfJbdAcWoFndintrF",
    "bodySize": 224,
    "bodyRows": 8,
    "bodyFormat": "csv",
    "numErrors": 1,
    "commitTime": "2001-01-01T01:01:01.000000001Z"
  }
]`
	if diff := cmp.Diff(expect, output); diff != "" {
		t.Errorf("unexpected (-want +got):\n%s", diff)
	}
}

// Test that a dataset name with bad upper-case characters, if it already exists, produces a
// warning but not an error when you try to Get it
func TestBadCaseIsJustWarning(t *testing.T) {
	run := NewTestRunner(t, "test_peer_qri_get_bad_case", "qri_get_bad_case")
	defer run.Delete()

	// Construct a dataset in order to have an existing version in the repo.
	ds := dataset.Dataset{
		Structure: &dataset.Structure{
			Format: "json",
			Schema: dataset.BaseSchemaArray,
		},
	}
	ds.SetBodyFile(qfs.NewMemfileBytes("body.json", []byte("[[\"one\",2],[\"three\",4]]")))

	// Add the dataset to the repo directly, which avoids the name validation check.
	run.AddDatasetToRefstore(t, "test_peer_qri_get_bad_case/a_New_Dataset", &ds)

	// Save the dataset, which will work now that a version already exists.
	err := run.ExecCommand("qri get test_peer_qri_get_bad_case/a_New_Dataset")
	if err != nil {
		t.Errorf("expect no error (just a warning), got %q", err)
	}
}

func TestSetupHappensBeforeOtherCommands(t *testing.T) {
	ctx := context.Background()

	qriHome := createTmpQriHome(t)
	cmd, shutdown := newCommand(ctx, qriHome, repotest.NewTestCrypto())

	cmdTextList := [7]string{"qri connect", "qri diff", "qri get", "qri init", "qri list", "qri pull", "qri search"}
	expect := "no qri repo exists\nhave you run 'qri setup'?"

	for _, cmdText := range cmdTextList {
		noRepoErr := executeCommand(cmd, cmdText)
		if noRepoErr.Error() != expect {
			timedShutdown(fmt.Sprintf("ExecCommand: %q\n", cmdText), shutdown)
			err := fmt.Sprintf("expected error for command %v:\n %v\n but received error:\n %v", cmdText, expect, noRepoErr)
			t.Fatal(err)
		}

	}
}
