package config

import (
	"io"
	"os"
	"testing"

	"blitiri.com.ar/go/chasquid/internal/testlib"
	"blitiri.com.ar/go/log"
	"github.com/google/go-cmp/cmp"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/testing/protocmp"
)

func mustCreateConfig(t *testing.T, contents string) (string, string) {
	tmpDir := testlib.MustTempDir(t)
	confStr := []byte(contents)
	err := os.WriteFile(tmpDir+"/chasquid.conf", confStr, 0600)
	if err != nil {
		t.Fatalf("Failed to write tmp config: %v", err)
	}

	return tmpDir, tmpDir + "/chasquid.conf"
}

func TestEmptyStruct(t *testing.T) {
	testLogConfig(&Config{})
}

func TestEmptyConfig(t *testing.T) {
	tmpDir, path := mustCreateConfig(t, "")
	defer testlib.RemoveIfOk(t, tmpDir)
	c, err := Load(path, "")
	if err != nil {
		t.Fatalf("error loading empty config: %v", err)
	}

	// Test the default values are set.
	defaults := proto.Clone(defaultConfig).(*Config)
	hostname, _ := os.Hostname()
	defaults.Hostname = hostname

	diff := cmp.Diff(defaults, c, protocmp.Transform())
	if diff != "" {
		t.Errorf("Load() mismatch (-want +got):\n%s", diff)
	}

	testLogConfig(c)
}

func TestFullConfig(t *testing.T) {
	confStr := `
		hostname: "joust"
		smtp_address: ":1234"
		smtp_address: ":5678"
		submission_address: ":10001"
		submission_address: ":10002"
		monitoring_address: ":1111"
		max_data_size_mb: 26
		suffix_separators: ""
		max_queue_items: 345
	`

	tmpDir, path := mustCreateConfig(t, confStr)
	defer testlib.RemoveIfOk(t, tmpDir)

	overrideStr := `
		hostname: "proust"
		submission_address: ":999"
		dovecot_auth: true
		drop_characters: ""
		give_up_send_after: "7h"
	`

	expected := &Config{
		Hostname:      "proust",
		MaxDataSizeMb: 26,

		SmtpAddress:              []string{":1234", ":5678"},
		SubmissionAddress:        []string{":999"},
		SubmissionOverTlsAddress: []string{"systemd"},
		MonitoringAddress:        ":1111",

		MailDeliveryAgentBin:  "maildrop",
		MailDeliveryAgentArgs: []string{"-f", "%from%", "-d", "%to_user%"},

		DataDir: "/var/lib/chasquid",

		SuffixSeparators: proto.String(""),
		DropCharacters:   proto.String(""),

		MailLogPath: "<syslog>",

		DovecotAuth: true,

		MaxQueueItems:   345,
		GiveUpSendAfter: "7h",
	}

	c, err := Load(path, overrideStr)
	if err != nil {
		t.Fatalf("error loading non-existent config: %v", err)
	}

	diff := cmp.Diff(expected, c, protocmp.Transform())
	if diff != "" {
		t.Errorf("Load() mismatch (-want +got):\n%s", diff)
	}

	testLogConfig(c)
}

func TestErrorLoading(t *testing.T) {
	c, err := Load("/does/not/exist", "")
	if err == nil {
		t.Fatalf("loaded a non-existent config: %v", c)
	}
}

func TestBrokenConfig(t *testing.T) {
	tmpDir, path := mustCreateConfig(
		t, "<invalid> this is not a valid protobuf")
	defer testlib.RemoveIfOk(t, tmpDir)

	c, err := Load(path, "")
	if err == nil {
		t.Fatalf("loaded an invalid config: %v", c)
	}
}

func TestBrokenOverride(t *testing.T) {
	tmpDir, path := mustCreateConfig(
		t, `hostname: "test"`)
	defer testlib.RemoveIfOk(t, tmpDir)

	c, err := Load(path, "broken override")
	if err == nil {
		t.Fatalf("loaded an invalid config: %v", c)
	}
}

func TestInvalidGiveUpSendingAfter(t *testing.T) {
	tmpDir, path := mustCreateConfig(
		t, `give_up_send_after: "10"`)
	defer testlib.RemoveIfOk(t, tmpDir)

	c, err := Load(path, "")
	if err == nil {
		t.Fatalf("loaded an invalid config: %v", c)
	}
}

// Run LogConfig, overriding the default logger first. This exercises the
// code, we don't yet validate the output, but it is an useful sanity check.
func testLogConfig(c *Config) {
	l := log.New(nopWCloser{io.Discard})
	log.Default = l
	LogConfig(c)
}

type nopWCloser struct {
	io.Writer
}

func (nopWCloser) Close() error { return nil }
