package authentication

import (
	"crypto/subtle"
	"fmt"
	"strings"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)

type Mode string

const (
	ModeDisabled Mode = "disabled"
	ModeObserve  Mode = "observe"
	ModeEnforce  Mode = "enforce"
)

const (
	StatusAccepted          = "accepted"
	StatusRejected          = "rejected"
	StatusUnknownAccount    = "unknown_account"
	StatusMissingCredential = "missing_credential"
	StatusUnconfigured      = "unconfigured"
)

type Result struct {
	Applicable bool
	Allowed    bool
	Mode       Mode
	Status     string
	Source     string
}

type Authenticator interface {
	Authenticate(envelope.FrameEnvelope) Result
}

func ParseMode(value string, fallback Mode) (Mode, error) {
	value = strings.ToLower(strings.TrimSpace(value))
	if value == "" {
		value = string(fallback)
	}
	switch Mode(value) {
	case ModeDisabled, ModeObserve, ModeEnforce:
		return Mode(value), nil
	default:
		return "", fmt.Errorf("unsupported authentication mode %q", value)
	}
}

type GB32960PlatformAuthenticator struct {
	mode        Mode
	credentials map[string][]string
}

func NewGB32960PlatformAuthenticator(mode Mode, credentials map[string][]string) *GB32960PlatformAuthenticator {
	normalized := make(map[string][]string, len(credentials))
	for username, passwords := range credentials {
		username = strings.TrimSpace(username)
		if username == "" {
			continue
		}
		for _, password := range passwords {
			if password == "" {
				continue
			}
			normalized[username] = append(normalized[username], password)
		}
	}
	return &GB32960PlatformAuthenticator{mode: mode, credentials: normalized}
}

func (a *GB32960PlatformAuthenticator) Authenticate(env envelope.FrameEnvelope) Result {
	if env.Protocol != envelope.ProtocolGB32960 || env.MessageID != "0x05" || a == nil || a.mode == ModeDisabled {
		return Result{}
	}
	login := nestedMap(env.Parsed, "platform_login")
	username := strings.TrimSpace(textValue(login, "username"))
	password := textValue(login, "password")
	status := StatusRejected
	switch {
	case len(a.credentials) == 0:
		status = StatusUnconfigured
	case username == "" || password == "":
		status = StatusMissingCredential
	default:
		expected, ok := a.credentials[username]
		if !ok || len(expected) == 0 {
			status = StatusUnknownAccount
		} else if anyConstantTimeEqual(expected, password) {
			status = StatusAccepted
		}
	}
	source := "none"
	if status == StatusAccepted {
		source = "configured"
	}
	return resultForMode(a.mode, status, source)
}

type JT808Authenticator struct {
	mode         Mode
	authCode     string
	deviceTokens JT808DeviceTokenProvider
}

type JT808DeviceTokenProvider interface {
	JT808AuthToken(phone string) (string, bool)
}

func NewJT808Authenticator(mode Mode, authCode string, deviceTokens JT808DeviceTokenProvider) *JT808Authenticator {
	return &JT808Authenticator{mode: mode, authCode: authCode, deviceTokens: deviceTokens}
}

func (a *JT808Authenticator) Authenticate(env envelope.FrameEnvelope) Result {
	if env.Protocol != envelope.ProtocolJT808 || env.MessageID != "0x0102" || a == nil || a.mode == ModeDisabled {
		return Result{}
	}
	token := textValue(nestedMap(env.Parsed, "authentication"), "token")
	status := StatusRejected
	source := "none"
	switch {
	case token == "":
		status = StatusMissingCredential
	default:
		if a.authCode != "" && constantTimeEqual(a.authCode, token) {
			status = StatusAccepted
			source = "configured"
			break
		}
		deviceToken, knownDevice := "", false
		if a.deviceTokens != nil {
			deviceToken, knownDevice = a.deviceTokens.JT808AuthToken(env.Phone)
		}
		if knownDevice && deviceToken != "" && constantTimeEqual(deviceToken, token) {
			status = StatusAccepted
			source = "device"
		} else if a.authCode == "" && !knownDevice {
			status = StatusUnconfigured
		}
	}
	return resultForMode(a.mode, status, source)
}

func Apply(env *envelope.FrameEnvelope, result Result) {
	if env == nil || !result.Applicable {
		return
	}
	env.AuthenticationMode = string(result.Mode)
	env.AuthenticationStatus = result.Status
	env.AuthenticationEnforced = result.Mode == ModeEnforce
}

// RedactParsedCredentials removes convenience copies of secrets before parsed
// fields are flattened and published. The original protocol frame remains in
// raw_hex for restricted forensic access.
func RedactParsedCredentials(env *envelope.FrameEnvelope) {
	if env == nil || env.Protocol != envelope.ProtocolGB32960 {
		return
	}
	login := nestedMap(env.Parsed, "platform_login")
	if login == nil {
		return
	}
	if password := textValue(login, "password"); password != "" {
		login["password_present"] = true
	}
	delete(login, "password")
}

func resultForMode(mode Mode, status string, source ...string) Result {
	allowed := status == StatusAccepted || mode != ModeEnforce
	credentialSource := "none"
	if len(source) > 0 && strings.TrimSpace(source[0]) != "" {
		credentialSource = strings.TrimSpace(source[0])
	}
	return Result{Applicable: true, Allowed: allowed, Mode: mode, Status: status, Source: credentialSource}
}

func constantTimeEqual(expected string, actual string) bool {
	if len(expected) != len(actual) {
		return false
	}
	return subtle.ConstantTimeCompare([]byte(expected), []byte(actual)) == 1
}

func anyConstantTimeEqual(expected []string, actual string) bool {
	matched := 0
	for _, candidate := range expected {
		if len(candidate) == len(actual) {
			matched |= subtle.ConstantTimeCompare([]byte(candidate), []byte(actual))
		}
	}
	return matched == 1
}

func nestedMap(parent map[string]any, key string) map[string]any {
	if parent == nil {
		return nil
	}
	value, _ := parent[key].(map[string]any)
	return value
}

func textValue(values map[string]any, key string) string {
	if values == nil {
		return ""
	}
	value, ok := values[key]
	if !ok || value == nil {
		return ""
	}
	return strings.TrimSpace(fmt.Sprint(value))
}
