package pkg

import (
	"bytes"
	"encoding/hex"
	"path/filepath"
	"strings"
	"testing"

	"gitlab.com/M0M097/pkg/lib/sboRepo"
	"gitlab.com/M0M097/pkg/lib/utils"
)

func TestPackageErrors_Add(t *testing.T) {
	eh := &packageErrors{}
	p := &sboRepo.Package{}

	eh.add(p, "some reason")
	if len(*eh) != 1 {
		t.Errorf("expected 1 error message, got %d", len(*eh))
	}

	want := p.Name() + ": some reason"
	if (*eh)[0] != want {
		t.Errorf("expected error '%s', got '%s'", want, (*eh)[0])
	}
}

func TestPackageErrors_SkipPkgAndDependees(t *testing.T) {
	var (
		eh = &packageErrors{}
		p  = sboRepo.PkgCreator{ // e.g., returns a *Package with p.name set, etc.
			Name: DUMMY_PKG1,                                  // name
			Path: filepath.Join(t.TempDir(), "test-category"), // path
		}.Create()
		_ = mockDependees(t, p, DUMMY_PKG2, DUMMY_PKG3)
	)

	eh.skipPkgAndDependees(p, "skipped reason")

	if len(*eh) != 3 {
		t.Errorf("expected 3 error messages, got %d. Messages: %v", len(*eh), *eh)
		return
	}
	// The order should be:
	//   1) name: skipped reason
	//   2) dependee1: Depends on name
	//   3) dependee2: Depends on name
	mustBeEqual(t, (*eh)[0], DUMMY_PKG1+": skipped reason")
	mustBeEqual(t, (*eh)[1], DUMMY_PKG2+": Depends on "+DUMMY_PKG1)
	mustBeEqual(t, (*eh)[2], DUMMY_PKG3+": Depends on "+DUMMY_PKG1)

	// Ensure p is removed from the list:
	if p.IsInList() {
		t.Errorf("expected package to be removed from the list, but it's still in list")
	}
}

func TestPackageErrors_CheckDownloads_FileNotFound(t *testing.T) {
	const DOWNLOAD_NAME = "foo.tar.gz"
	var (
		eh           packageErrors
		categoryPath = filepath.Join(t.TempDir(), "test-category")

		p = sboRepo.PkgCreator{
			Name:     DUMMY_PKG1,
			Path:     categoryPath,
			Download: DOWNLOAD_NAME,
		}.Create() // e.g., returns a *Package with p.name set, etc.
		head = mockDependees(t, p, DUMMY_PKG2, DUMMY_PKG3)
	)

	eh.checkDownloads(head, p.Path())

	if len(eh) != 3 { // 1 for the package, 2 for the dependees
		t.Fatalf("expected 1 error message, got %d. Errors: \n%s", len(eh), eh)
	}

	want := filepath.Join(p.Path(), DOWNLOAD_NAME) + ": file not found"
	if !strings.Contains((eh)[0], want) {
		t.Errorf("expected error to contain '%s', got '%s'", want, (eh)[0])
	}
}

func TestPackageErrors_CheckDownloads_Success(t *testing.T) {
	// We want to create a dummy file that matches the expected MD5.
	// Let's pick some known file content:
	const (
		FILE_CONTENT  = "Hello from the test file"
		DOWNLOAD_NAME = "foo.tar.gz"
	)
	var (
		correctMD5 = hex.EncodeToString(utils.MD5Sum(strings.NewReader(FILE_CONTENT)))
		eh         = &packageErrors{}

		// Set up the .info file with the correct MD5.
		p = sboRepo.PkgCreator{ // e.g., returns a *Package with p.name set, etc.
			Name:     DUMMY_PKG3,
			Path:     t.TempDir(),
			Version:  DUMMY_VERSION,
			Download: DOWNLOAD_NAME,
			Md5sum:   correctMD5,
		}.Create()
		head = sboRepo.NewListHead()

		pPath    = p.Path()
		fullPath = filepath.Join(pPath, DOWNLOAD_NAME)
	)

	// Now actually create the "download" file in p.path with the matching content.
	p.Append(head)
	utils.Content2File(fullPath, FILE_CONTENT)

	// Run the check
	eh.checkDownloads(head, pPath)

	// If everything is correct, we expect zero errors
	if len(*eh) != 0 {
		t.Fatalf("expected no errors, got %d errors. Errors:\n%s", len(*eh), *eh)
	}
}

func TestPackageErrors_CheckDownloads_MD5Mismatch(t *testing.T) {
	const (
		FILE_CONTENT  = "Hello from mismatch land"
		DOWNLOAD_NAME = "foo.tar.gz"
		WRONG_MD5     = "00000000000000000000000000000000" // definitely not correctMD5
	)
	var (
		eh packageErrors

		repo         = t.TempDir() // Use a temporary directory for the repo
		categoryPath = filepath.Join(repo, "test-category")
		p            = sboRepo.PkgCreator{ // e.g., returns a *Package with p.name set, etc.
			Name:     DUMMY_PKG1,
			Path:     categoryPath,
			Download: DOWNLOAD_NAME,
			Md5sum:   WRONG_MD5,
		}.Create()
		head     = mockDependees(t, p, DUMMY_PKG2, DUMMY_PKG3)
		pPath    = p.Path()
		fullPath = filepath.Join(pPath, DOWNLOAD_NAME)
	)

	// Now actually create the "download" file in pPath with the matching content.
	utils.Content2File(fullPath, FILE_CONTENT)

	// Run the check
	eh.checkDownloads(head, pPath)

	// 1 error for the package, plus errors for each "dependee" => total 3.
	if len(eh) != 3 {
		t.Fatalf("expected 3 errors (1 for the package + 2 for the dependee), got %d. Errors:\n%s", len(eh), eh)
	}

	// Check the first error message mentions 'MD5Sum Mismatch'
	want := filepath.Join(pPath, DOWNLOAD_NAME) + ": MD5Sum Mismatch"
	if !strings.Contains((eh)[0], want) {
		t.Errorf("expected error to contain '%s', got '%s'", want, (eh)[0])
	}
}

func TestPackageErrors_Final_NoErrors(t *testing.T) {
	eh := &packageErrors{}
	var buf bytes.Buffer

	eh.final(&buf)
	got := buf.String()
	want := "All packages installed successfully\n"
	if got != want {
		t.Errorf("expected '%s', got '%s'", want, got)
	}
}

func TestPackageErrors_Final_WithErrors(t *testing.T) {
	eh := &packageErrors{
		"some error 1",
		"some error 2",
	}
	var buf bytes.Buffer

	eh.final(&buf)
	got := buf.String()

	// We expect the Banner(...) output
	if !strings.Contains(got, "Skipped Packages") {
		t.Error("expected output to contain 'Skipped Packages'")
	}
	if !strings.Contains(got, "some error 1") {
		t.Error("expected output to contain 'some error 1'")
	}
	if !strings.Contains(got, "some error 2") {
		t.Error("expected output to contain 'some error 2'")
	}
}

func mockDependees(t *testing.T, p *sboRepo.Package, pkgNames ...string) sboRepo.Head {
	categoryPath := filepath.Dir(p.Path())
	for _, pName := range pkgNames {
		sboRepo.PkgCreator{
			Name: pName, Path: categoryPath, Requires: p.Name(),
		}.Create()
	}
	rm := sboRepo.NewRepoMap(filepath.Dir(categoryPath))
	head := rm.ProcessList(pkgNames)
	*p = *rm.Get(p.Name()) // This is necessary so that non-exported fields which
	// might have been changed in ProcessList are updated in p
	return head
}
