//go:build linux && (amd64 || arm64)

/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

package file

import (
	"os"
	"reflect"
	"testing"

	"golang.zabbix.com/agent2/pkg/zbxtest"
)

func TestFileRegmatch(t *testing.T) {
	var ctx zbxtest.MockEmptyCtx
	filename := "/tmp/zbx_vfs_file_regmatch_test.dat"

	type testCase struct {
		fileContents   []byte
		targetSearch   string
		targetEncoding string
		lineStart      string
		lineEnd        string
		match          int
	}

	//августа\r\n
	fileContents_1_ISO_8859_5 := []byte{0xd0, 0xd2, 0xd3, 0xe3, 0xe1, 0xe2, 0xd0, 0x0d, 0x0a}

	// badger
	// error
	// badger2
	fileContents_1_UTF_16LE := []byte{
		0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x0a, 0x00, 0x65, 0x00,
		0x72, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00,
		0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x32, 0x00, 0x0a, 0x00}

	// выхухоль
	//
	// badger
	//
	// выхухоль2
	fileContents_2_ISO_8859_5 := []byte{
		0xd2, 0xeb, 0xe5, 0xe3, 0xe5, 0xde, 0xdb, 0xec, 0x0a, 0x0a, 0x62, 0x61, 0x64, 0x67, 0x65, 0x72,
		0x0a, 0x0a, 0xd2, 0xeb, 0xe5, 0xe3, 0xe5, 0xde, 0xdb, 0xec, 0x32, 0x0a}

	fileContents_2_UTF_16LE := []byte{
		0x32, 0x04, 0x4b, 0x04, 0x45, 0x04, 0x43, 0x04, 0x45, 0x04, 0x3e, 0x04, 0x3b, 0x04, 0x4c, 0x04,
		0x0a, 0x00, 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x64, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
		0x0a, 0x00, 0x0a, 0x00, 0x32, 0x04, 0x4b, 0x04, 0x45, 0x04, 0x43, 0x04, 0x45, 0x04, 0x3e, 0x04,
		0x3b, 0x04, 0x4c, 0x04, 0x32, 0x00, 0x0a, 0x00}

	fileContents_UTF_32BE := []byte{
		0x00, 0x00, 0x04, 0x32, 0x00, 0x00, 0x04, 0x4b, 0x00, 0x00, 0x04, 0x45, 0x00, 0x00, 0x04, 0x43,
		0x00, 0x00, 0x04, 0x45, 0x00, 0x00, 0x04, 0x3e, 0x00, 0x00, 0x04, 0x3b, 0x00, 0x00, 0x04, 0x4c,
		0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x61,
		0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72,
		0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x04, 0x32, 0x00, 0x00, 0x04, 0x4b,
		0x00, 0x00, 0x04, 0x45, 0x00, 0x00, 0x04, 0x43, 0x00, 0x00, 0x04, 0x45, 0x00, 0x00, 0x04, 0x3e,
		0x00, 0x00, 0x04, 0x3b, 0x00, 0x00, 0x04, 0x4c, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0a}

	// 127.0.0.1 localhost
	// 127.0.1.1 zabbix
	//
	hostsFile := []byte{
		0x31, 0x00, 0x32, 0x00, 0x37, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x30, 0x00, 0x2e, 0x00,
		0x31, 0x00, 0x20, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x68, 0x00,
		0x6f, 0x00, 0x73, 0x00, 0x74, 0x00, 0x0a, 0x00, 0x31, 0x00, 0x32, 0x00, 0x37, 0x00, 0x2e, 0x00,
		0x30, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x20, 0x00, 0x7a, 0x00, 0x61, 0x00,
		0x62, 0x00, 0x62, 0x00, 0x69, 0x00, 0x78, 0x00, 0x0a, 0x00, 0x0a, 0x00}

	// 127.0.0.1 локалхост
	// 127.0.1.1 заббикс
	//
	hostsFile_RU := []byte{
		0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
		0x30, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
		0x31, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x3b, 0x04, 0x00, 0x00, 0x3e, 0x04, 0x00, 0x00,
		0x3a, 0x04, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00, 0x3b, 0x04, 0x00, 0x00, 0x45, 0x04, 0x00, 0x00,
		0x3e, 0x04, 0x00, 0x00, 0x41, 0x04, 0x00, 0x00, 0x42, 0x04, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
		0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
		0x30, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00,
		0x31, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x37, 0x04, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00,
		0x31, 0x04, 0x00, 0x00, 0x31, 0x04, 0x00, 0x00, 0x38, 0x04, 0x00, 0x00, 0x3a, 0x04, 0x00, 0x00,
		0x41, 0x04, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00}

	tests := []*testCase{
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "(а)", targetEncoding: "iso-8859-5",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileContents_1_UTF_16LE, targetSearch: "(error)", targetEncoding: "UTF-16LE",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileContents_2_ISO_8859_5, targetSearch: "хух", targetEncoding: "iso-8859-5",
			lineStart: "2", lineEnd: "", match: 1},
		{fileContents: fileContents_2_ISO_8859_5, targetSearch: "хух", targetEncoding: "iso-8859-5",
			lineStart: "1", lineEnd: "", match: 1},
		{fileContents: fileContents_2_ISO_8859_5, targetSearch: "выхухоль2\n", targetEncoding: "iso-8859-5",
			lineStart: "", lineEnd: "2", match: 0},
		{fileContents: fileContents_2_UTF_16LE, targetSearch: "хух", targetEncoding: "UTF-16LE",
			lineStart: "2", lineEnd: "", match: 1},
		{fileContents: fileContents_2_UTF_16LE, targetSearch: "хух", targetEncoding: "UTF-16LE",
			lineStart: "1", lineEnd: "", match: 1},
		{fileContents: fileContents_2_UTF_16LE, targetSearch: "выхухоль2\n", targetEncoding: "UTF-16LE",
			lineStart: "", lineEnd: "2", match: 0},
		{fileContents: fileContents_UTF_32BE, targetSearch: "хух", targetEncoding: "UTF-32BE",
			lineStart: "2", lineEnd: "", match: 1},
		{fileContents: fileContents_UTF_32BE, targetSearch: "хух", targetEncoding: "UTF-32BE",
			lineStart: "1", lineEnd: "", match: 1},
		{fileContents: fileContents_UTF_32BE, targetSearch: "выхухоль2\n", targetEncoding: "UTF-32BE",
			lineStart: "", lineEnd: "2", match: 0},

		{fileContents: []byte("127.0.0.1 localhost\n127.0.1.1 zabbix\n\n"), targetSearch: "localhost",
			targetEncoding: "", lineStart: "", lineEnd: "", match: 1},

		{fileContents: hostsFile, targetSearch: "localhost",
			targetEncoding: "UTF-16LE", lineStart: "", lineEnd: "", match: 1},

		{fileContents: hostsFile, targetSearch: "ll",
			targetEncoding: "UTF-16LE", lineStart: "", lineEnd: "", match: 0},

		{fileContents: hostsFile, targetSearch: "zabbix",
			targetEncoding: "UTF-16LE", lineStart: "", lineEnd: "", match: 1},

		{fileContents: hostsFile, targetSearch: "локалхост",
			targetEncoding: "UTF-16LE", lineStart: "", lineEnd: "", match: 0},

		{fileContents: hostsFile_RU, targetSearch: "локалхост",
			targetEncoding: "UTF-32LE", lineStart: "", lineEnd: "", match: 1},

		// wrong file encodings, but we cannot detect this and there is no expected match
		{fileContents: fileContents_1_UTF_16LE, targetSearch: "(error)", targetEncoding: "iso-8859-5",
			lineStart: "", lineEnd: "", match: 0},
		{fileContents: fileContents_UTF_32BE, targetSearch: "хух", targetEncoding: "iso-8859-5",
			lineStart: "2", lineEnd: "", match: 0},
		{fileContents: fileContents_2_UTF_16LE, targetSearch: "хух", targetEncoding: "UTF-32BE",
			lineStart: "2", lineEnd: "", match: 0},
		{fileContents: fileContents_UTF_32BE, targetSearch: "хух", targetEncoding: "UTF-16LE",
			lineStart: "1", lineEnd: "", match: 0}}
	for i, c := range tests {
		if err1 := os.WriteFile(filename, c.fileContents, 0644); err1 != nil {
			t.Errorf("failed to created file: %s", err1.Error())

			return
		}

		var result interface{}
		var err error

		if result, err = impl.Export("vfs.file.regmatch", []string{filename, c.targetSearch, c.targetEncoding,
			c.lineStart, c.lineEnd}, ctx); err != nil {
			t.Errorf("vfs.file.regmatch[%d] returned error %s", i, err.Error())

			return
		}

		var match int
		var ok bool

		if match, ok = result.(int); !ok {
			t.Errorf("vfs.file.regmatch returned unexpected value type %s",
				reflect.TypeOf(result).Kind())

			return
		}

		if match != c.match {
			t.Errorf("vfs.file.regmatch testcase[%d] returned invalid result: %d,"+
				" while expected: %d", i, match, c.match)
		}
	}

	// a
	fileSingleCharNoNewLine := []byte{0x61}

	// alphabeta
	fileManyCharsNoNewLine := []byte{
		0x61, 0x6c, 0x70, 0x68, 0x61, 0x62, 0x65, 0x74, 0x61}

	// wrong encodings in file, but we can detect this
	testsWrongEncodingsInFile := []*testCase{
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "(а)", targetEncoding: "UTF-16LE",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "(а)", targetEncoding: "UTF-32BE",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileSingleCharNoNewLine, targetSearch: "a", targetEncoding: "UTF-16LE",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileManyCharsNoNewLine, targetSearch: "a", targetEncoding: "UTF-32BE",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "хух", targetEncoding: "UTF-32BE",
			lineStart: "2", lineEnd: "", match: 0}}
	expectedError := "Cannot read from file. Wrong encoding detected."
	for i, c := range testsWrongEncodingsInFile {
		if err1 := os.WriteFile(filename, c.fileContents, 0644); err1 != nil {
			t.Errorf("failed to created file: %s", err1.Error())

			return
		}

		if _, err := impl.Export("vfs.file.regmatch", []string{filename, c.targetSearch, c.targetEncoding,
			c.lineStart, c.lineEnd}, ctx); err != nil {
			if err.Error() != expectedError {
				t.Errorf(`vfs.file.regmatch testcase[%d] failed with unexpected error: %s,
					expected: %s`, i, err.Error(), expectedError)
			}
		} else {
			t.Errorf("vfs.file.regmatch testcase[%d] did NOT return error", i)
		}
	}

	// wrong targets encodings
	testsWrongTargetEncodings := []*testCase{
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "(а)", targetEncoding: "BADGER",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "(а)", targetEncoding: "UTF-16L",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "(а)", targetEncoding: "UTF-",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileSingleCharNoNewLine, targetSearch: "a", targetEncoding: "UUTF-32BE",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileManyCharsNoNewLine, targetSearch: "a", targetEncoding: "TF-32BE",
			lineStart: "", lineEnd: "", match: 1},
		{fileContents: fileContents_1_ISO_8859_5, targetSearch: "хух", targetEncoding: "-32",
			lineStart: "2", lineEnd: "", match: 0}}
	for i, c := range testsWrongTargetEncodings {
		if err1 := os.WriteFile(filename, c.fileContents, 0644); err1 != nil {
			t.Errorf("failed to created file: %s", err1.Error())

			return
		}

		var err error
		_, err = impl.Export("vfs.file.regmatch", []string{filename, c.targetSearch, c.targetEncoding,
			c.lineStart, c.lineEnd}, ctx)

		if nil == err {
			t.Errorf("vfs.file.regmatch (testCase[%d]) did not return error: ->%s<- when wrong target "+
				"encoding:->%s<- was used", i, expectedErrorUTF8Convert, c.targetEncoding)

			return
		} else if err.Error() != expectedErrorUTF8Convert {
			t.Errorf("vfs.file.regmatch (testCase[%d]) expected error: ->%s<-,"+
				"but it instead returned: %s when wrong target encoding: ->%s<- was used", i,
				expectedErrorUTF8Convert, err.Error(), c.targetEncoding)

			return
		}
	}
}