package testhelper

import (
	"archive/zip"
	"bufio"
	"bytes"
	"encoding/xml"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"testing"

	"go.devnw.com/ooxml"
)

var update = flag.Bool("test.update", false, "update golden file")

func CompareGoldenXML(t *testing.T, expectedFn string, got []byte) {
	golden := filepath.Join("testdata", expectedFn)
	if *update {
		if err := ioutil.WriteFile(golden, got, 0644); err != nil {
			t.Fatal(err)
		}
	}
	exp, err := ioutil.ReadFile(golden)
	if err != nil {
		t.Fatalf("unable to read expected input: %s", err)
	}
	dumpXmlDiff(t, exp, got)
}

func CompareZip(t *testing.T, expectedFn string, got []byte, cmpFileContents bool) {
	golden := filepath.Join("testdata", expectedFn)
	zgot, err := zip.NewReader(bytes.NewReader(got), int64(len(got)))
	if err != nil {
		t.Fatalf("unable to read test input: %s", err)
	}

	f, err := os.Open(golden)
	if err != nil {
		t.Fatalf("unable to read golden file: %s", err)
	}
	defer f.Close()

	fi, err := os.Stat(golden)
	if err != nil {
		t.Errorf("unable to read file: %s", err)
	}
	zexp, err := zip.NewReader(f, fi.Size())
	if err != nil {
		t.Errorf("unable to read file: %s", err)
	}
	t.Run(expectedFn, compareZipContents(zexp, zgot, cmpFileContents))
}

func CompareGoldenZipFilesOnly(t *testing.T, expectedFn string, got []byte) {
	golden := filepath.Join("testdata", expectedFn)
	if *update {
		if err := ioutil.WriteFile(golden, got, 0644); err != nil {
			t.Fatal(err)
		}
	}
	CompareZip(t, expectedFn, got, false)
}

func CompareGoldenZip(t *testing.T, expectedFn string, got []byte) {
	golden := filepath.Join("testdata", expectedFn)
	if *update {
		if err := ioutil.WriteFile(golden, got, 0644); err != nil {
			t.Fatal(err)
		}
	}
	CompareZip(t, expectedFn, got, true)
}

func compareZipContents(exp, got *zip.Reader, cmpFileContents bool) func(t *testing.T) {
	return func(t *testing.T) {
		expFiles := make([]*zip.File, len(exp.File))
		copy(expFiles, exp.File)
		gotFiles := make([]*zip.File, len(got.File))
		copy(gotFiles, got.File)
		if len(expFiles) != len(gotFiles) {
			t.Errorf("expected %d files, got %d", len(exp.File), len(got.File))
		}
		// check the list of files
		for i, f := range expFiles {
			for j, g := range gotFiles {
				if g == nil {
					continue
				}
				if f.Name == g.Name {
					if cmpFileContents {
						// comparing contents that have the same name
						t.Run(f.Name, compareFiles(f, g))
					}
					expFiles[i] = nil
					gotFiles[j] = nil
				}
			}
		}
		// and warning about any files that differ in name
		for _, f := range expFiles {
			if f != nil {
				t.Errorf("didn't find expected file '%s'", f.Name)
			}
		}
		for _, g := range gotFiles {
			if g != nil {
				t.Errorf("found unexpected file '%s'", g.Name)
			}
		}
	}
}

func compareFiles(exp, got *zip.File) func(t *testing.T) {
	return func(t *testing.T) {
		ef, err := exp.Open()
		if err != nil {
			t.Errorf("error opening %s", exp.Name)
		}
		defer ef.Close()
		gf, err := got.Open()
		if err != nil {
			t.Errorf("error opening %s", got.Name)
		}
		defer gf.Close()

		expAll, _ := ioutil.ReadAll(ef)
		gotAll, _ := ioutil.ReadAll(gf)
		if !bytes.Equal(expAll, gotAll) {
			dumpXmlDiff(t, expAll, gotAll)
			fmt.Println(string(expAll))
			fmt.Println(string(gotAll))
			t.Errorf("mismatched contents %d vs %d", len(expAll), len(gotAll))
		}

	}
}

func tempFilePath(prefix string) string {
	expF, _ := ioutil.TempFile("", prefix)
	defer expF.Close()
	return expF.Name()
}

func xmlIndentFile(fn string) error {
	any := office.XSDAny{}
	f, err := os.Open(fn)
	if err != nil {
		return err
	}
	dec := xml.NewDecoder(f)
	if err = dec.Decode(&any); err != nil {
		return err
	}
	f.Close()
	f, err = os.Create(fn)
	if err != nil {
		return err
	}
	defer f.Close()
	enc := xml.NewEncoder(f)
	enc.Indent("", "  ")
	return enc.Encode(&any)
}

func dumpXmlDiff(t *testing.T, exp, got []byte) {
	expF := tempFilePath("expected")
	ioutil.WriteFile(expF, exp, 0644)
	gotF := tempFilePath("got")
	ioutil.WriteFile(gotF, got, 0644)

	xmlIndentFile(expF)
	xmlIndentFile(gotF)

	diff := exec.Command("diff", "-u", expF, gotF)
	outp, err := diff.StdoutPipe()
	if err != nil {
		t.Fatalf("error running xmlindent: %s", err)
	}
	defer outp.Close()
	errp, err := diff.StderrPipe()
	if err != nil {
		t.Fatalf("error running xmlindent: %s", err)
	}
	defer errp.Close()

	if err := diff.Start(); err != nil {
		t.Fatalf("error string xmlindent: %s", err)
	}
	scanner := bufio.NewScanner(outp)
	for scanner.Scan() {
		log.Println(scanner.Text())
	}

	if err := diff.Wait(); err != nil {
		errOutput, _ := ioutil.ReadAll(errp)
		t.Fatalf("error waiting on xmlindent: %s [%s]", string(errOutput), err)
	}
}
