//go:build integration
// +build integration

/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
** documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all copies or substantial portions
** of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
** WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
** COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
** SOFTWARE.
**/

package tlsconfig

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"math/big"
	"os"
	"path/filepath"
	"testing"
	"time"
)

// TestDetails_LoadCertificates and TestDetails_GetTLSConfig require filesystem access.
// We use a helper to create temporary self-signed certs for these tests.
// returns caPath, certPath, keyPath, cleanup.
func createTestCerts(t *testing.T) (string, string, string, func()) {
	t.Helper()

	tempDir := t.TempDir()

	// Set up CA certificate.
	ca := &x509.Certificate{
		SerialNumber:          big.NewInt(2025),
		Subject:               pkix.Name{Organization: []string{"Test CA"}},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().AddDate(0, 0, 1),
		IsCA:                  true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
	}

	caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		t.Fatalf("failed to generate ca key: %v", err)
	}

	caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
	if err != nil {
		t.Fatalf("failed to create ca cert: %v", err)
	}

	// Write CA to file.
	caPath := filepath.Join(tempDir, "ca.pem")

	caFile, err := os.Create(filepath.Clean(caPath))
	if err != nil {
		t.Fatalf("failed to create ca file: %v", err)
	}

	defer caFile.Close()

	_ = pem.Encode(caFile, &pem.Block{Type: "CERTIFICATE", Bytes: caBytes})

	// Write Key to file.
	keyPath := filepath.Join(tempDir, "key.pem")

	keyFile, err := os.Create(filepath.Clean(keyPath))
	if err != nil {
		t.Fatalf("failed to create key file: %v", err)
	}

	defer keyFile.Close()

	_ = pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey)})

	// Write Cert to file (can use the CA cert itself for simplicity in this test).
	certPath := filepath.Join(tempDir, "cert.pem")

	certFile, err := os.Create(filepath.Clean(certPath))
	if err != nil {
		t.Fatalf("failed to create cert file: %v", err)
	}

	defer certFile.Close()

	_ = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: caBytes})

	cleanup := func() {
		err = os.RemoveAll(tempDir)
		if err != nil {
			return
		}
	}

	return caPath, certPath, keyPath, cleanup
}

func TestDetails_LoadCertificates(t *testing.T) {
	t.Parallel()

	_, certPath, keyPath, cleanup := createTestCerts(t)
	t.Cleanup(cleanup)

	t.Run("no cert or key file provided", func(t *testing.T) {
		t.Parallel()

		d := Details{}

		certs, err := d.LoadCertificates()
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}

		if certs != nil {
			t.Fatalf("expected nil certificates, got %v", certs)
		}
	})

	t.Run("invalid cert file path", func(t *testing.T) {
		t.Parallel()

		d := Details{TLSCertFile: "nonexistent.pem", TLSKeyFile: keyPath}

		_, err := d.LoadCertificates()
		if err == nil {
			t.Fatal("expected an error for nonexistent cert file, got nil")
		}
	})

	t.Run("invalid cert file path", func(t *testing.T) {
		t.Parallel()

		d := Details{TLSCertFile: "nonexistent.pem", TLSKeyFile: keyPath}

		_, err := d.LoadCertificates()
		if err == nil {
			t.Fatal("expected an error for nonexistent cert file, got nil")
		}
	})

	t.Run("success loading valid certificates", func(t *testing.T) {
		t.Parallel()

		d := Details{TLSCertFile: certPath, TLSKeyFile: keyPath}

		certs, err := d.LoadCertificates()
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}

		if len(certs) != 1 {
			t.Fatalf("expected 1 certificate, got %d", len(certs))
		}
	})
}

//nolint:gocyclo,cyclop //these are unit tests
func TestDetails_GetTLSConfig(t *testing.T) {
	t.Parallel()

	caPath, certPath, keyPath, cleanup := createTestCerts(t)
	t.Cleanup(cleanup)

	t.Run("fail to read ca file", func(t *testing.T) {
		t.Parallel()

		d := Details{TLSCaFile: "nonexistent.pem"}

		_, err := d.GetTLSConfig()
		if err == nil {
			t.Fatal("expected an error for nonexistent ca file, got nil")
		}
	})

	t.Run("fail with invalid ca file content", func(t *testing.T) {
		t.Parallel()

		invalidCAPath := filepath.Join(t.TempDir(), "invalid.pem")

		err := os.WriteFile(invalidCAPath, []byte("not a pem"), 0600)
		if err != nil {
			return
		}

		d := Details{TLSCaFile: invalidCAPath}
		_, err = d.GetTLSConfig()

		if err == nil {
			t.Fatal("expected an error for invalid ca file content, got nil")
		}
	})

	t.Run("success without client certs", func(t *testing.T) {
		t.Parallel()

		d := Details{TLSCaFile: caPath, TLSServerName: "test.server"}

		config, err := d.GetTLSConfig()
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}

		if config.ServerName != "test.server" {
			t.Errorf("expected ServerName to be 'test.server', got '%s'", config.ServerName)
		}

		if config.InsecureSkipVerify {
			t.Error("expected InsecureSkipVerify to be false")
		}
	})

	t.Run("success with all files and SkipDefaultTLSVerification", func(t *testing.T) {
		t.Parallel()

		d := Details{
			TLSCaFile:                  caPath,
			TLSCertFile:                certPath,
			TLSKeyFile:                 keyPath,
			TLSServerName:              "test.server",
			SkipDefaultTLSVerification: true,
		}

		config, err := d.GetTLSConfig()
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}

		if !config.InsecureSkipVerify {
			t.Error("expected InsecureSkipVerify to be true")
		}

		if len(config.Certificates) != 1 {
			t.Errorf("expected 1 client certificate, got %d", len(config.Certificates))
		}
	})

	t.Run("invalid cert file path", func(t *testing.T) {
		t.Parallel()

		d := Details{TLSCaFile: caPath, TLSCertFile: "nonexistent.pem", TLSKeyFile: keyPath}

		_, err := d.GetTLSConfig()
		if err == nil {
			t.Fatal("expected an error for nonexistent cert file, got nil")
		}
	})
}
