package acp

import (
	"crypto/ed25519"
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"xworkmate-bridge/internal/gatewayruntime"
)

var bridgeGatewayIdentity = struct {
	sync.Mutex
	value       gatewayruntime.DeviceIdentity
	deviceToken string
}{}

type storedBridgeGatewayIdentity struct {
	Version             int    `json:"version"`
	DeviceID            string `json:"deviceId"`
	PublicKeyBase64URL  string `json:"publicKeyBase64Url"`
	PrivateKeyBase64URL string `json:"privateKeyBase64Url"`
	CreatedAtMs         int64  `json:"createdAtMs"`
	DeviceToken         string `json:"deviceToken,omitempty"`
}

func newBridgeGatewayIdentity() gatewayruntime.DeviceIdentity {
	identity, _ := bridgeGatewayOpenClawCredentials()
	return identity
}

func bridgeGatewayOpenClawCredentials() (gatewayruntime.DeviceIdentity, string) {
	bridgeGatewayIdentity.Lock()
	defer bridgeGatewayIdentity.Unlock()
	if strings.TrimSpace(bridgeGatewayIdentity.value.DeviceID) != "" {
		return bridgeGatewayIdentity.value, bridgeGatewayIdentity.deviceToken
	}
	identity, deviceToken, ok := loadBridgeGatewayIdentity()
	if !ok {
		identity = generateBridgeGatewayIdentity()
		deviceToken = ""
	}
	bridgeGatewayIdentity.value = identity
	bridgeGatewayIdentity.deviceToken = deviceToken
	return bridgeGatewayIdentity.value, bridgeGatewayIdentity.deviceToken
}

func generateBridgeGatewayIdentity() gatewayruntime.DeviceIdentity {
	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return gatewayruntime.DeviceIdentity{}
	}
	return gatewayruntime.DeviceIdentity{
		DeviceID:            deriveOpenClawDeviceID(publicKey),
		PublicKeyBase64URL:  base64.RawURLEncoding.EncodeToString(publicKey),
		PrivateKeyBase64URL: base64.RawURLEncoding.EncodeToString(privateKey),
	}
}

func loadBridgeGatewayIdentity() (gatewayruntime.DeviceIdentity, string, bool) {
	path := bridgeGatewayIdentityPath()
	data, err := os.ReadFile(path)
	if err != nil {
		identity := generateBridgeGatewayIdentity()
		return identity, "", persistBridgeGatewayIdentity(path, identity, "")
	}
	var stored storedBridgeGatewayIdentity
	if err := json.Unmarshal(data, &stored); err != nil {
		identity := generateBridgeGatewayIdentity()
		return identity, "", persistBridgeGatewayIdentity(path, identity, "")
	}
	identity := gatewayruntime.DeviceIdentity{
		DeviceID:            strings.TrimSpace(stored.DeviceID),
		PublicKeyBase64URL:  strings.TrimSpace(stored.PublicKeyBase64URL),
		PrivateKeyBase64URL: strings.TrimSpace(stored.PrivateKeyBase64URL),
	}
	if normalized, ok := normalizeBridgeGatewayIdentity(identity); ok {
		if normalized.DeviceID != identity.DeviceID {
			_ = persistBridgeGatewayIdentity(path, normalized, strings.TrimSpace(stored.DeviceToken))
		}
		return normalized, strings.TrimSpace(stored.DeviceToken), true
	}
	identity = generateBridgeGatewayIdentity()
	return identity, "", persistBridgeGatewayIdentity(path, identity, "")
}

func normalizeBridgeGatewayIdentity(identity gatewayruntime.DeviceIdentity) (gatewayruntime.DeviceIdentity, bool) {
	publicKey, err := decodeRawBase64URL(identity.PublicKeyBase64URL)
	if err != nil || len(publicKey) != ed25519.PublicKeySize {
		return gatewayruntime.DeviceIdentity{}, false
	}
	privateKey, err := decodeRawBase64URL(identity.PrivateKeyBase64URL)
	if err != nil {
		return gatewayruntime.DeviceIdentity{}, false
	}
	var edPrivateKey ed25519.PrivateKey
	switch len(privateKey) {
	case ed25519.PrivateKeySize:
		edPrivateKey = ed25519.PrivateKey(privateKey)
	case ed25519.SeedSize:
		edPrivateKey = ed25519.NewKeyFromSeed(privateKey)
	default:
		return gatewayruntime.DeviceIdentity{}, false
	}
	derivedPublic, ok := edPrivateKey.Public().(ed25519.PublicKey)
	if !ok || subtle.ConstantTimeCompare(derivedPublic, publicKey) != 1 {
		return gatewayruntime.DeviceIdentity{}, false
	}
	identity.DeviceID = deriveOpenClawDeviceID(publicKey)
	identity.PublicKeyBase64URL = base64.RawURLEncoding.EncodeToString(publicKey)
	identity.PrivateKeyBase64URL = base64.RawURLEncoding.EncodeToString(edPrivateKey)
	return identity, true
}

func saveBridgeGatewayDeviceToken(deviceToken string) {
	deviceToken = strings.TrimSpace(deviceToken)
	if deviceToken == "" {
		return
	}
	bridgeGatewayIdentity.Lock()
	defer bridgeGatewayIdentity.Unlock()
	if strings.TrimSpace(bridgeGatewayIdentity.value.DeviceID) == "" {
		return
	}
	bridgeGatewayIdentity.deviceToken = deviceToken
	_ = persistBridgeGatewayIdentity(
		bridgeGatewayIdentityPath(),
		bridgeGatewayIdentity.value,
		deviceToken,
	)
}

func persistBridgeGatewayIdentity(
	path string,
	identity gatewayruntime.DeviceIdentity,
	deviceToken string,
) bool {
	if strings.TrimSpace(identity.DeviceID) == "" ||
		strings.TrimSpace(identity.PublicKeyBase64URL) == "" ||
		strings.TrimSpace(identity.PrivateKeyBase64URL) == "" {
		return false
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return false
	}
	_ = os.Chmod(filepath.Dir(path), 0o700)
	payload := storedBridgeGatewayIdentity{
		Version:             1,
		DeviceID:            identity.DeviceID,
		PublicKeyBase64URL:  identity.PublicKeyBase64URL,
		PrivateKeyBase64URL: identity.PrivateKeyBase64URL,
		CreatedAtMs:         time.Now().UnixMilli(),
		DeviceToken:         strings.TrimSpace(deviceToken),
	}
	data, err := json.MarshalIndent(payload, "", "  ")
	if err != nil {
		return false
	}
	data = append(data, '\n')
	if err := os.WriteFile(path, data, 0o600); err != nil {
		return false
	}
	_ = os.Chmod(path, 0o600)
	return true
}

func bridgeGatewayIdentityPath() string {
	if path := strings.TrimSpace(os.Getenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH")); path != "" {
		return path
	}
	home, err := os.UserHomeDir()
	if err != nil || strings.TrimSpace(home) == "" {
		home = "."
	}
	return filepath.Join(home, ".xworkmate-bridge", "openclaw-device.json")
}

func deriveOpenClawDeviceID(publicKey ed25519.PublicKey) string {
	sum := sha256.Sum256(publicKey)
	return hex.EncodeToString(sum[:])
}

func isOpenClawMode(mode string) bool {
	return strings.TrimSpace(strings.ToLower(mode)) == "openclaw"
}

func decodeRawBase64URL(value string) ([]byte, error) {
	return base64.RawURLEncoding.DecodeString(strings.TrimSpace(value))
}
