// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.

package resource

import (
	"context"
	"fmt"
	"github.com/stretchr/testify/assert"
	"log"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
	"time"
)

func TestNewDocumentLoader(t *testing.T) {
	d := NewDocument()
	assert.NotEqual(t, nil, d)
}

func TestDocumentLoader(t *testing.T) {
	dir, err := os.MkdirTemp("", "testdocumentloader")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(dir)

	file, _ := filepath.Abs(filepath.Join(dir, "foo.txt"))

	document := fmt.Sprintf(`
---
resources:
- type: file
  attributes:
    path: "%s"
    owner: "nobody"
    group: "nobody"
    mode: "0600"
    content: |-
      test line 1
      test line 2
    state: present
- type: user
  attributes:
    name: "testuser"
    uid: "10022"
    group: "10022"
    home: "/home/testuser"
    createhome: true
    state: present
`, file)
	d := NewDocument()
	assert.NotEqual(t, nil, d)

	docReader := strings.NewReader(document)

	e := d.Load(docReader)
	assert.Nil(t, e)

	resources := d.Resources()
	assert.Equal(t, 2, len(resources))
}

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

	fileContent := `// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
`

	file, _ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))

	err := os.WriteFile(file, []byte(fileContent), 0644)
	assert.Nil(t, err)

	info, statErr := os.Stat(file)
	assert.Nil(t, statErr)
	mTime := info.ModTime()
	stat, ok := info.Sys().(*syscall.Stat_t)
	assert.True(t, ok)

	aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
	cTime := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))

	expected := fmt.Sprintf(`
resources:
  - type: file
    attributes:
      path: %s
      owner: "root"
      group: "root"
      mode: "0644"
      content: |
        %s
      atime: %s
      ctime: %s
      mtime: %s
      sha256: ea33e2082ca777f82dc9571b08df95d81925eed04e1bdbac7cdc6dc52d330eca
      size: 82
      filetype: "regular"
      state: present
`, file, fileContent, aTime.Format(time.RFC3339Nano), cTime.Format(time.RFC3339Nano), mTime.Format(time.RFC3339Nano))

	var documentYaml strings.Builder
	d := NewDocument()
	assert.NotEqual(t, nil, d)

	f, e := ResourceTypes.New("file://")
	assert.Nil(t, e)
	assert.NotNil(t, f)

	f.(*File).Path = filepath.Join(TempDir, "foo.txt")
	_,readErr := f.(*File).Read(ctx)
	assert.Nil(t, readErr)
	d.AddResourceDeclaration("file", f)
	ey := d.Generate(&documentYaml)
	assert.Equal(t, nil, ey)

	assert.Greater(t, documentYaml.Len(), 0)
	assert.YAMLEq(t, expected, documentYaml.String())
}

func TestDocumentAddResource(t *testing.T) {
	file, _ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))
	err := os.WriteFile(file, []byte(""), 0644)
	assert.Nil(t, err)

	d := NewDocument()
	assert.NotNil(t, d)
	e := d.AddResource(fmt.Sprintf("file://%s", file))
	assert.Nil(t, e)
}

func TestDocumentJSON(t *testing.T) {
	document := `
---
resources:
- type: user
  attributes:
    name: "testuser"
    uid: "10022"
    group: "10022"
    home: "/home/testuser"
    createhome: true
    state: present
`
	d := NewDocument()
	assert.NotNil(t, d)
	docReader := strings.NewReader(document)

	e := d.Load(docReader)
	assert.Nil(t, e)

	marshalledJSON, jsonErr := d.JSON()
	assert.Nil(t, jsonErr)
	assert.Greater(t, len(marshalledJSON), 0)
}

func TestDocumentJSONSchema(t *testing.T) {
	document := NewDocument()
	document.ResourceDecls = []Declaration{}
	e := document.Validate()
	assert.Nil(t, e)
}

func TestDocumentYAML(t *testing.T) {
	document := `
---
resources:
- type: user
  attributes:
    name: "testuser"
    uid: "10022"
    group: "10022"
    home: "/home/testuser"
    createhome: true
    state: present
`
	d := NewDocument()
	assert.NotNil(t, d)
	docReader := strings.NewReader(document)

	e := d.Load(docReader)
	assert.Nil(t, e)

	marshalledYAML, yamlErr := d.YAML()
	assert.Nil(t, yamlErr)
	assert.YAMLEq(t, string(document), string(marshalledYAML))

}

func TestDocumentResourceFilter(t *testing.T) {
	document := `
---
resources:
- type: user
  attributes:
    name: "testuser"
    uid: "10022"
    home: "/home/testuser"
    state: present
- type: file
  attributes:
    path: "foo.txt"
    state: present
- type: file
  attributes:
    path: "bar.txt"
    state: present
`

	d := NewDocument()
	assert.NotNil(t, d)
	docReader := strings.NewReader(document)

	e := d.Load(docReader)
	assert.Nil(t, e)
	
	resources := d.Filter(func(d *Declaration) bool {
		return d.Type == "file"
	})
	assert.Equal(t, 2, len(resources))
}
