package config

import (
	"encoding/json"
	"fmt"
	"testing"
	"time"
)

var expiry = 1 * time.Minute

var invalidProfileConfig = &Config{
	Signing: &Signing{
		Profiles: map[string]*SigningProfile{
			"invalid": {
				Usage:  []string{"wiretapping"},
				Expiry: expiry,
			},
			"empty": {},
			"invalid-lint": {
				Usage:        []string{"digital signature"},
				Expiry:       expiry,
				LintErrLevel: 9000,
			},
		},
		Default: &SigningProfile{
			Usage:  []string{"digital signature"},
			Expiry: expiry,
		},
	},
}

var invalidDefaultConfig = &Config{
	Signing: &Signing{
		Profiles: map[string]*SigningProfile{
			"key usage": {
				Usage: []string{"digital signature"},
			},
		},
		Default: &SigningProfile{
			Usage: []string{"s/mime"},
		},
	},
}

var validConfig = &Config{
	Signing: &Signing{
		Profiles: map[string]*SigningProfile{
			"valid": {
				Usage:  []string{"digital signature"},
				Expiry: expiry,
			},
			"valid-lint": {
				Usage:              []string{"digital signature"},
				Expiry:             expiry,
				LintErrLevel:       5,
				ExcludeLints:       []string{"n_subject_common_name_included"},
				ExcludeLintSources: []string{"ETSI-ESI"},
			},
		},
		Default: &SigningProfile{
			Usage:  []string{"digital signature"},
			Expiry: expiry,
		},
	},
}

var validMixedConfig = `
{
	"signing": {
		"profiles": {
			"CA": {
				"auth_key": "sample",
				"remote": "localhost"
			},
			"email": {
				"usages": ["s/mime"],
				"expiry": "720h"
			}
		},
		"default": {
			"usages": ["digital signature", "email protection"],
			"expiry": "8000h"
		}
	},
	"auth_keys": {
		"sample": {
			"type":"standard",
			"key":"0123456789ABCDEF0123456789ABCDEF"
		}
	},
	"remotes": {
		"localhost": "127.0.0.1:8888"
	}
}`

var validMinimalRemoteConfig = `
{
	"signing": {
		"default": {
			"auth_key": "sample",
			"remote": "localhost"
		}
	},
	"auth_keys": {
		"sample": {
			"type":"standard",
			"key":"0123456789ABCDEF0123456789ABCDEF"
		}
	},
	"remotes": {
		"localhost": "127.0.0.1:8888"
	}
}`

var validMinimalRemoteConfig2 = `
{
	"signing": {
		"default": {
			"auth_remote":{
			    "auth_key": "sample",
			    "remote": "localhost"
		    }
		}
	},
	"auth_keys": {
		"sample": {
			"type":"standard",
			"key":"0123456789ABCDEF0123456789ABCDEF"
		}
	},
	"remotes": {
		"localhost": "127.0.0.1:8888"
	}
}`

var invalidConflictRemoteConfig = `
{
	"signing": {
		"default": {
			"auth_remote":{
			    "auth_key": "sample",
			    "remote": "localhost"
		    },
			"remote": "localhost"
		}
	},
	"auth_keys": {
		"sample": {
			"type":"standard",
			"key":"0123456789ABCDEF0123456789ABCDEF"
		}
	},
	"remotes": {
		"localhost": "127.0.0.1:8888"
	}
}`

var invalidRemoteConfig = `
{
	"signing": {
		"default": {
			"auth_remotes_typos":{
			    "auth_key": "sample",
			    "remote": "localhost"
		    }
		}
	},
	"auth_keys": {
		"sample": {
			"type":"standard",
			"key":"0123456789ABCDEF0123456789ABCDEF"
		}
	},
	"remotes": {
		"localhost": "127.0.0.1:8888"
	}
}`

var invalidAuthRemoteConfigMissingRemote = `
{
	"signing": {
		"default": {
			"auth_remote":{
			    "auth_key": "sample"
		    }
		}
	},
	"auth_keys": {
		"sample": {
			"type":"standard",
			"key":"0123456789ABCDEF0123456789ABCDEF"
		}
	},
	"remotes": {
		"localhost": "127.0.0.1:8888"
	}
}`

var invalidAuthRemoteConfigMissingKey = `
{
	"signing": {
		"default": {
			"auth_remote":{
			    "remote": "localhost"
		    }
		}
	},
	"auth_keys": {
		"sample": {
			"type":"standard",
			"key":"0123456789ABCDEF0123456789ABCDEF"
		}
	},
	"remotes": {
		"localhost": "127.0.0.1:8888"
	}
}`

var validMinimalLocalConfig = `
{
	"signing": {
		"default": {
			"usages": ["digital signature", "email protection"],
			"expiry": "8000h"
		}
	}
}`

var validLocalConfigsWithCAConstraint = []string{
	`{
		"signing": {
			"default": {
				"usages": ["digital signature", "email protection"],
				"ca_constraint": { "is_ca": true },
				"expiry": "8000h"
			}
		}
	}`,
	`{
		"signing": {
			"default": {
				"usages": ["digital signature", "email protection"],
				"ca_constraint": { "is_ca": true, "max_path_len": 1 },
				"expiry": "8000h"
			}
		}
	}`,
	`{
		"signing": {
			"default": {
				"usages": ["digital signature", "email protection"],
				"ca_constraint": { "is_ca": true, "max_path_len_zero": true },
				"expiry": "8000h"
			}
		}
	}`,
}

var copyExtensionWantedlLocalConfig = `
{
	"signing": {
		"default": {
			"expiry": "8000h",
			"copy_extensions": true
		}
	}
}`

var copyExtensionNotWantedlLocalConfig = `
{
	"signing": {
		"default": {
			"expiry": "8000h"
		}
	}
}`

func TestInvalidProfile(t *testing.T) {
	if invalidProfileConfig.Signing.Profiles["invalid"].validProfile(false) {
		t.Fatal("invalid profile accepted as valid")
	}

	if invalidProfileConfig.Signing.Profiles["empty"].validProfile(false) {
		t.Fatal("invalid profile accepted as valid")
	}

	if invalidProfileConfig.Signing.Profiles["invalid-lint"].validProfile(false) {
		t.Fatal("invalid profile accepted as valid")
	}

	if invalidProfileConfig.Valid() {
		t.Fatal("invalid config accepted as valid")
	}

	if !invalidProfileConfig.Signing.Profiles["invalid"].validProfile(true) {
		t.Fatal("invalid profile should be a valid default profile")
	}
}

func TestRemoteProfiles(t *testing.T) {
	var validRemoteProfile = &SigningProfile{
		RemoteName:   "localhost",
		RemoteServer: "localhost:8080",
	}

	var invalidRemoteProfile = &SigningProfile{
		RemoteName: "localhost",
	}

	var invalidRemoteAuthProfile = &SigningProfile{
		RemoteName:   "localhost",
		RemoteServer: "localhost:8080",
		AuthKeyName:  "blahblah",
	}

	if !validRemoteProfile.validProfile(true) ||
		!validRemoteProfile.validProfile(false) {
		t.Fatal("valid remote profile is rejected.")
	}

	if invalidRemoteProfile.validProfile(true) ||
		invalidRemoteProfile.validProfile(false) {
		t.Fatal("invalid remote profile is accepted.")
	}

	if invalidRemoteAuthProfile.validProfile(true) ||
		invalidRemoteAuthProfile.validProfile(false) {
		t.Fatal("invalid remote profile is accepted.")
	}
}

func TestInvalidDefault(t *testing.T) {
	if invalidDefaultConfig.Signing.Default.validProfile(true) {
		t.Fatal("invalid default accepted as valid")
	}

	if invalidDefaultConfig.Valid() {
		t.Fatal("invalid config accepted as valid")
	}

	if !invalidDefaultConfig.Signing.Default.validProfile(false) {
		t.Fatal("invalid default profile should be a valid profile")
	}
}

func TestValidConfig(t *testing.T) {
	if !validConfig.Valid() {
		t.Fatal("Valid config is not valid")
	}
	bytes, _ := json.Marshal(validConfig)
	fmt.Printf("%v", string(bytes))
}

func TestDefaultConfig(t *testing.T) {
	if !DefaultConfig().validProfile(false) {
		t.Fatal("global default signing profile should be a valid profile.")
	}

	if !DefaultConfig().validProfile(true) {
		t.Fatal("global default signing profile should be a valid default profile")
	}
}

func TestParse(t *testing.T) {
	var validProfiles = []*SigningProfile{
		{
			ExpiryString: "8760h",
		},
		{
			ExpiryString: "168h",
		},
		{
			ExpiryString: "300s",
		},
	}

	var invalidProfiles = []*SigningProfile{
		nil,
		{},
		{
			ExpiryString: "",
		},
		{
			ExpiryString: "365d",
		},
		{
			ExpiryString: "1y",
		},
		{
			ExpiryString: "one year",
		},
	}

	for _, p := range validProfiles {
		if p.populate(nil) != nil {
			t.Fatalf("Failed to parse ExpiryString=%s", p.ExpiryString)
		}
	}

	for _, p := range invalidProfiles {
		if p.populate(nil) == nil {
			if p != nil {
				t.Fatalf("ExpiryString=%s should not be parsable", p.ExpiryString)
			}
			t.Fatalf("Nil profile should not be parsable")
		}
	}

}

func TestPopulateLintRegistry(t *testing.T) {
	excludedLintName := "n_subject_common_name_included"
	etsiLintName := "w_qcstatem_qctype_web"
	profile := &SigningProfile{
		ExpiryString:       "300s",
		ExcludeLints:       []string{excludedLintName},
		ExcludeLintSources: []string{"ETSI_ESI"},
	}

	if err := profile.populate(nil); err != nil {
		t.Fatal("unexpected error from profile populate")
	}

	// The LintRegistry shouldn't be nil.
	if profile.LintRegistry == nil {
		t.Errorf("expected to find non-nil lint registry after populate()")
	}

	// The excluded lint shouldn't be found in the registry
	if l := profile.LintRegistry.ByName(excludedLintName); l != nil {
		t.Errorf("expected lint name %q to be filtered out, found %v", excludedLintName, l)
	}

	// A lint from the excluded source category shouldn't be found in the registry.
	if l := profile.LintRegistry.ByName(etsiLintName); l != nil {
		t.Errorf("expected lint name %q to be filtered out, found %v", etsiLintName, l)
	}
}

func TestLoadFile(t *testing.T) {
	validConfigFiles := []string{
		"testdata/valid_config.json",
		"testdata/valid_config_auth.json",
		"testdata/valid_config_no_default.json",
		"testdata/valid_config_auth_no_default.json",
	}

	for _, configFile := range validConfigFiles {
		_, err := LoadFile(configFile)
		if err != nil {
			t.Fatal("Load valid config file failed.", configFile, "error is ", err)
		}
	}
}

func TestLoadInvalidConfigFile(t *testing.T) {
	invalidConfigFiles := []string{"", "testdata/no_such_file",
		"testdata/invalid_default.json",
		"testdata/invalid_profiles.json",
		"testdata/invalid_usage.json",
		"testdata/invalid_config.json",
		"testdata/invalid_auth.json",
		"testdata/invalid_auth_bad_key.json",
		"testdata/invalid_no_auth_keys.json",
		"testdata/invalid_remote.json",
		"testdata/invalid_no_remotes.json",
	}
	for _, configFile := range invalidConfigFiles {
		_, err := LoadFile(configFile)
		if err == nil {
			t.Fatal("Invalid config is loaded.", configFile)
		}
	}
}

func TestNeedLocalSigner(t *testing.T) {

	c, err := LoadConfig([]byte(validMixedConfig))
	if err != nil {
		t.Fatal("load valid config failed:", err)
	}

	// This signing config needs both local signer and remote signer.
	if c.Signing.NeedsLocalSigner() != true {
		t.Fatal("incorrect NeedsLocalSigner().")
	}

	if c.Signing.NeedsRemoteSigner() != true {
		t.Fatal("incorrect NeedsRemoteSigner()")
	}

	remoteConfig, err := LoadConfig([]byte(validMinimalRemoteConfig))
	if err != nil {
		t.Fatal("Load valid config failed:", err)
	}

	if remoteConfig.Signing.NeedsLocalSigner() != false {
		t.Fatal("incorrect NeedsLocalSigner().")
	}

	if remoteConfig.Signing.NeedsRemoteSigner() != true {
		t.Fatal("incorrect NeedsRemoteSigner().")
	}

	localConfig, err := LoadConfig([]byte(validMinimalLocalConfig))
	if localConfig.Signing.NeedsLocalSigner() != true {
		t.Fatal("incorrect NeedsLocalSigner().")
	}

	if localConfig.Signing.NeedsRemoteSigner() != false {
		t.Fatal("incorrect NeedsRemoteSigner().")
	}

	if err != nil {
		t.Fatal(err)
	}
}

func TestOverrideRemotes(t *testing.T) {
	c, err := LoadConfig([]byte(validMixedConfig))
	if err != nil {
		t.Fatal("load valid config failed:", err)
	}

	host := "localhost:8888"
	c.Signing.OverrideRemotes(host)

	if c.Signing.Default.RemoteServer != host {
		t.Fatal("should override default profile's RemoteServer")
	}

	for _, p := range c.Signing.Profiles {
		if p.RemoteServer != host {
			t.Fatal("failed to override profile's RemoteServer")
		}
	}

}

func TestAuthRemoteConfig(t *testing.T) {
	c, err := LoadConfig([]byte(validMinimalRemoteConfig2))
	if err != nil {
		t.Fatal("load valid config failed:", err)
	}

	if c.Signing.Default.RemoteServer != "127.0.0.1:8888" {
		t.Fatal("load valid config failed: incorrect remote server")
	}

	host := "localhost:8888"
	c.Signing.OverrideRemotes(host)

	if c.Signing.Default.RemoteServer != host {
		t.Fatal("should override default profile's RemoteServer")
	}

	for _, p := range c.Signing.Profiles {
		if p.RemoteServer != host {
			t.Fatal("failed to override profile's RemoteServer")
		}
	}
}

func TestDuplicateRemoteConfig(t *testing.T) {
	_, err := LoadConfig([]byte(invalidConflictRemoteConfig))
	if err == nil {
		t.Fatal("fail to reject invalid config")
	}
}

func TestBadAuthRemoteConfig(t *testing.T) {
	_, err := LoadConfig([]byte(invalidRemoteConfig))
	if err == nil {
		t.Fatal("load invalid config should failed")
	}

	_, err = LoadConfig([]byte(invalidAuthRemoteConfigMissingRemote))
	if err == nil {
		t.Fatal("load invalid config should failed")
	}

	_, err = LoadConfig([]byte(invalidAuthRemoteConfigMissingKey))
	if err == nil {
		t.Fatal("load invalid config should failed")
	}

	var p *Signing
	if p.Valid() {
		t.Fatal("nil Signing config should be invalid")
	}
}

func TestValidCAConstraint(t *testing.T) {
	for _, config := range validLocalConfigsWithCAConstraint {
		_, err := LoadConfig([]byte(config))
		if err != nil {
			t.Fatal("can't parse valid ca constraint")
		}
	}
}

func TestWantCopyExtension(t *testing.T) {
	localConfig, err := LoadConfig([]byte(copyExtensionWantedlLocalConfig))
	if localConfig.Signing.Default.CopyExtensions != true {
		t.Fatal("incorrect TestWantCopyExtension().")
	}

	if err != nil {
		t.Fatal(err)
	}
}

func TestDontWantCopyExtension(t *testing.T) {
	localConfig, err := LoadConfig([]byte(copyExtensionNotWantedlLocalConfig))
	if localConfig.Signing.Default.CopyExtensions != false {
		t.Fatal("incorrect TestDontWantCopyExtension().")
	}

	if err != nil {
		t.Fatal(err)
	}
}
