package app

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"

	"git.mleku.dev/mleku/nostr/encoders/bech32encoding"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/httpauth"
	"lol.mleku.dev/chk"
	"lol.mleku.dev/log"

	"next.orly.dev/pkg/acl"
	"next.orly.dev/pkg/database"
)

// WireGuardConfigResponse is returned by the /api/wireguard/config endpoint.
type WireGuardConfigResponse struct {
	ConfigText string      `json:"config_text"`
	Interface  WGInterface `json:"interface"`
	Peer       WGPeer      `json:"peer"`
}

// WGInterface represents the [Interface] section of a WireGuard config.
type WGInterface struct {
	Address    string `json:"address"`
	PrivateKey string `json:"private_key"`
}

// WGPeer represents the [Peer] section of a WireGuard config.
type WGPeer struct {
	PublicKey  string `json:"public_key"`
	Endpoint   string `json:"endpoint"`
	AllowedIPs string `json:"allowed_ips"`
}

// BunkerURLResponse is returned by the /api/bunker/url endpoint.
type BunkerURLResponse struct {
	URL         string `json:"url"`
	RelayNpub   string `json:"relay_npub"`
	RelayPubkey string `json:"relay_pubkey"`
	InternalIP  string `json:"internal_ip"`
}

// handleWireGuardConfig returns the user's WireGuard configuration.
// Requires NIP-98 authentication and write+ access.
func (s *Server) handleWireGuardConfig(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Check if WireGuard is enabled
	if !s.Config.WGEnabled {
		http.Error(w, "WireGuard is not enabled on this relay", http.StatusNotFound)
		return
	}

	// Check if ACL mode supports WireGuard
	if s.Config.ACLMode == "none" {
		http.Error(w, "WireGuard requires ACL mode 'follows' or 'managed'", http.StatusForbidden)
		return
	}

	// Validate NIP-98 authentication
	valid, pubkey, err := httpauth.CheckAuth(r)
	if chk.E(err) || !valid {
		http.Error(w, "NIP-98 authentication required", http.StatusUnauthorized)
		return
	}

	// Check user has write+ access
	accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr)
	if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" {
		http.Error(w, "Write access required for WireGuard", http.StatusForbidden)
		return
	}

	// Type assert to Badger database for WireGuard methods
	badgerDB, ok := s.DB.(*database.D)
	if !ok {
		http.Error(w, "WireGuard requires Badger database backend", http.StatusInternalServerError)
		return
	}

	// Check subnet pool is available
	if s.subnetPool == nil {
		http.Error(w, "WireGuard subnet pool not initialized", http.StatusInternalServerError)
		return
	}

	// Get or create WireGuard peer for this user
	peer, err := badgerDB.GetOrCreateWireGuardPeer(pubkey, s.subnetPool)
	if chk.E(err) {
		log.E.F("failed to get/create WireGuard peer: %v", err)
		http.Error(w, "Failed to create WireGuard configuration", http.StatusInternalServerError)
		return
	}

	// Derive subnet IPs from sequence
	subnet := s.subnetPool.SubnetForSequence(peer.Sequence)
	clientIP := subnet.ClientIP.String()
	serverIP := subnet.ServerIP.String()

	// Get server public key
	serverKey, err := badgerDB.GetOrCreateWireGuardServerKey()
	if chk.E(err) {
		log.E.F("failed to get WireGuard server key: %v", err)
		http.Error(w, "WireGuard server not configured", http.StatusInternalServerError)
		return
	}

	serverPubKey, err := deriveWGPublicKey(serverKey)
	if chk.E(err) {
		log.E.F("failed to derive server public key: %v", err)
		http.Error(w, "WireGuard server error", http.StatusInternalServerError)
		return
	}

	// Build endpoint
	endpoint := fmt.Sprintf("%s:%d", s.Config.WGEndpoint, s.Config.WGPort)

	// Build response
	resp := WireGuardConfigResponse{
		Interface: WGInterface{
			Address:    clientIP + "/32",
			PrivateKey: base64.StdEncoding.EncodeToString(peer.WGPrivateKey),
		},
		Peer: WGPeer{
			PublicKey:  base64.StdEncoding.EncodeToString(serverPubKey),
			Endpoint:   endpoint,
			AllowedIPs: serverIP + "/32", // Only route bunker traffic to this peer's server IP
		},
	}

	// Generate config text
	resp.ConfigText = fmt.Sprintf(`[Interface]
Address = %s
PrivateKey = %s

[Peer]
PublicKey = %s
Endpoint = %s
AllowedIPs = %s
PersistentKeepalive = 25
`, resp.Interface.Address, resp.Interface.PrivateKey,
		resp.Peer.PublicKey, resp.Peer.Endpoint, resp.Peer.AllowedIPs)

	// If WireGuard server is running, add the peer
	if s.wireguardServer != nil && s.wireguardServer.IsRunning() {
		if err := s.wireguardServer.AddPeer(pubkey, peer.WGPublicKey, clientIP); chk.E(err) {
			log.W.F("failed to add peer to running WireGuard server: %v", err)
		}
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// handleWireGuardRegenerate generates a new WireGuard keypair for the user.
// Requires NIP-98 authentication and write+ access.
func (s *Server) handleWireGuardRegenerate(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Check if WireGuard is enabled
	if !s.Config.WGEnabled {
		http.Error(w, "WireGuard is not enabled on this relay", http.StatusNotFound)
		return
	}

	// Check if ACL mode supports WireGuard
	if s.Config.ACLMode == "none" {
		http.Error(w, "WireGuard requires ACL mode 'follows' or 'managed'", http.StatusForbidden)
		return
	}

	// Validate NIP-98 authentication
	valid, pubkey, err := httpauth.CheckAuth(r)
	if chk.E(err) || !valid {
		http.Error(w, "NIP-98 authentication required", http.StatusUnauthorized)
		return
	}

	// Check user has write+ access
	accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr)
	if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" {
		http.Error(w, "Write access required for WireGuard", http.StatusForbidden)
		return
	}

	// Type assert to Badger database for WireGuard methods
	badgerDB, ok := s.DB.(*database.D)
	if !ok {
		http.Error(w, "WireGuard requires Badger database backend", http.StatusInternalServerError)
		return
	}

	// Check subnet pool is available
	if s.subnetPool == nil {
		http.Error(w, "WireGuard subnet pool not initialized", http.StatusInternalServerError)
		return
	}

	// Remove old peer from running server if exists
	oldPeer, err := badgerDB.GetWireGuardPeer(pubkey)
	if err == nil && oldPeer != nil && s.wireguardServer != nil && s.wireguardServer.IsRunning() {
		s.wireguardServer.RemovePeer(oldPeer.WGPublicKey)
	}

	// Regenerate keypair
	peer, err := badgerDB.RegenerateWireGuardPeer(pubkey, s.subnetPool)
	if chk.E(err) {
		log.E.F("failed to regenerate WireGuard peer: %v", err)
		http.Error(w, "Failed to regenerate WireGuard configuration", http.StatusInternalServerError)
		return
	}

	// Derive subnet IPs from sequence (same sequence as before)
	subnet := s.subnetPool.SubnetForSequence(peer.Sequence)
	clientIP := subnet.ClientIP.String()

	log.I.F("regenerated WireGuard keypair for user: %s", hex.Enc(pubkey[:8]))

	// Return success with IP (same subnet as before)
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"status":      "regenerated",
		"assigned_ip": clientIP,
	})
}

// handleBunkerURL returns the bunker connection URL.
// Requires NIP-98 authentication and write+ access.
func (s *Server) handleBunkerURL(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Check if bunker is enabled
	if !s.Config.BunkerEnabled {
		http.Error(w, "Bunker is not enabled on this relay", http.StatusNotFound)
		return
	}

	// Check if WireGuard is enabled (required for bunker)
	if !s.Config.WGEnabled {
		http.Error(w, "WireGuard is required for bunker access", http.StatusNotFound)
		return
	}

	// Check if ACL mode supports WireGuard
	if s.Config.ACLMode == "none" {
		http.Error(w, "Bunker requires ACL mode 'follows' or 'managed'", http.StatusForbidden)
		return
	}

	// Validate NIP-98 authentication
	valid, pubkey, err := httpauth.CheckAuth(r)
	if chk.E(err) || !valid {
		http.Error(w, "NIP-98 authentication required", http.StatusUnauthorized)
		return
	}

	// Check user has write+ access
	accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr)
	if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" {
		http.Error(w, "Write access required for bunker", http.StatusForbidden)
		return
	}

	// Type assert to Badger database for WireGuard methods
	badgerDB, ok := s.DB.(*database.D)
	if !ok {
		http.Error(w, "Bunker requires Badger database backend", http.StatusInternalServerError)
		return
	}

	// Check subnet pool is available
	if s.subnetPool == nil {
		http.Error(w, "WireGuard subnet pool not initialized", http.StatusInternalServerError)
		return
	}

	// Get or create WireGuard peer to get their subnet
	peer, err := badgerDB.GetOrCreateWireGuardPeer(pubkey, s.subnetPool)
	if chk.E(err) {
		log.E.F("failed to get/create WireGuard peer for bunker: %v", err)
		http.Error(w, "Failed to get WireGuard configuration", http.StatusInternalServerError)
		return
	}

	// Derive server IP for this peer's subnet
	subnet := s.subnetPool.SubnetForSequence(peer.Sequence)
	serverIP := subnet.ServerIP.String()

	// Get relay identity
	relaySecret, err := s.DB.GetOrCreateRelayIdentitySecret()
	if chk.E(err) {
		log.E.F("failed to get relay identity: %v", err)
		http.Error(w, "Failed to get relay identity", http.StatusInternalServerError)
		return
	}

	relayPubkey, err := deriveNostrPublicKey(relaySecret)
	if chk.E(err) {
		log.E.F("failed to derive relay public key: %v", err)
		http.Error(w, "Failed to derive relay public key", http.StatusInternalServerError)
		return
	}

	// Encode as npub
	relayNpubBytes, err := bech32encoding.BinToNpub(relayPubkey)
	relayNpub := string(relayNpubBytes)
	if chk.E(err) {
		relayNpub = hex.Enc(relayPubkey) // Fallback to hex
	}

	// Build bunker URL using this peer's server IP
	// Format: bunker://<relay-pubkey-hex>?relay=ws://<server-ip>:3335
	relayPubkeyHex := hex.Enc(relayPubkey)
	bunkerURL := fmt.Sprintf("bunker://%s?relay=ws://%s:%d",
		relayPubkeyHex,
		serverIP,
		s.Config.BunkerPort,
	)

	resp := BunkerURLResponse{
		URL:         bunkerURL,
		RelayNpub:   relayNpub,
		RelayPubkey: relayPubkeyHex,
		InternalIP:  serverIP,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// handleWireGuardStatus returns whether WireGuard/Bunker are available.
func (s *Server) handleWireGuardStatus(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	resp := map[string]interface{}{
		"wireguard_enabled": s.Config.WGEnabled,
		"bunker_enabled":    s.Config.BunkerEnabled,
		"acl_mode":          s.Config.ACLMode,
		"available":         s.Config.WGEnabled && s.Config.ACLMode != "none",
	}

	if s.wireguardServer != nil {
		resp["wireguard_running"] = s.wireguardServer.IsRunning()
		resp["peer_count"] = s.wireguardServer.PeerCount()
	}

	if s.bunkerServer != nil {
		resp["bunker_sessions"] = s.bunkerServer.SessionCount()
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// RevokedKeyResponse is the JSON response for revoked keys.
type RevokedKeyResponse struct {
	NostrPubkey  string `json:"nostr_pubkey"`
	WGPublicKey  string `json:"wg_public_key"`
	Sequence     uint32 `json:"sequence"`
	ClientIP     string `json:"client_ip"`
	ServerIP     string `json:"server_ip"`
	CreatedAt    int64  `json:"created_at"`
	RevokedAt    int64  `json:"revoked_at"`
	AccessCount  int    `json:"access_count"`
	LastAccessAt int64  `json:"last_access_at"`
}

// AccessLogResponse is the JSON response for access logs.
type AccessLogResponse struct {
	NostrPubkey string `json:"nostr_pubkey"`
	WGPublicKey string `json:"wg_public_key"`
	Sequence    uint32 `json:"sequence"`
	ClientIP    string `json:"client_ip"`
	Timestamp   int64  `json:"timestamp"`
	RemoteAddr  string `json:"remote_addr"`
}

// handleWireGuardAudit returns the user's own revoked keys and access logs.
// This lets users see if their old WireGuard keys are still being used,
// which could indicate they left something on or someone copied their credentials.
// Requires NIP-98 authentication and write+ access.
func (s *Server) handleWireGuardAudit(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Check if WireGuard is enabled
	if !s.Config.WGEnabled {
		http.Error(w, "WireGuard is not enabled on this relay", http.StatusNotFound)
		return
	}

	// Validate NIP-98 authentication
	valid, pubkey, err := httpauth.CheckAuth(r)
	if chk.E(err) || !valid {
		http.Error(w, "NIP-98 authentication required", http.StatusUnauthorized)
		return
	}

	// Check user has write+ access (same as other WireGuard endpoints)
	accessLevel := acl.Registry.GetAccessLevel(pubkey, r.RemoteAddr)
	if accessLevel != "write" && accessLevel != "admin" && accessLevel != "owner" {
		http.Error(w, "Write access required", http.StatusForbidden)
		return
	}

	// Type assert to Badger database for WireGuard methods
	badgerDB, ok := s.DB.(*database.D)
	if !ok {
		http.Error(w, "WireGuard requires Badger database backend", http.StatusInternalServerError)
		return
	}

	// Check subnet pool is available
	if s.subnetPool == nil {
		http.Error(w, "WireGuard subnet pool not initialized", http.StatusInternalServerError)
		return
	}

	// Get this user's revoked keys only
	revokedKeys, err := badgerDB.GetRevokedKeys(pubkey)
	if chk.E(err) {
		log.E.F("failed to get revoked keys: %v", err)
		http.Error(w, "Failed to get revoked keys", http.StatusInternalServerError)
		return
	}

	// Get this user's access logs only
	accessLogs, err := badgerDB.GetAccessLogs(pubkey)
	if chk.E(err) {
		log.E.F("failed to get access logs: %v", err)
		http.Error(w, "Failed to get access logs", http.StatusInternalServerError)
		return
	}

	// Convert to response format
	var revokedResp []RevokedKeyResponse
	for _, key := range revokedKeys {
		subnet := s.subnetPool.SubnetForSequence(key.Sequence)
		revokedResp = append(revokedResp, RevokedKeyResponse{
			NostrPubkey:  hex.Enc(key.NostrPubkey),
			WGPublicKey:  hex.Enc(key.WGPublicKey),
			Sequence:     key.Sequence,
			ClientIP:     subnet.ClientIP.String(),
			ServerIP:     subnet.ServerIP.String(),
			CreatedAt:    key.CreatedAt,
			RevokedAt:    key.RevokedAt,
			AccessCount:  key.AccessCount,
			LastAccessAt: key.LastAccessAt,
		})
	}

	var accessResp []AccessLogResponse
	for _, logEntry := range accessLogs {
		subnet := s.subnetPool.SubnetForSequence(logEntry.Sequence)
		accessResp = append(accessResp, AccessLogResponse{
			NostrPubkey: hex.Enc(logEntry.NostrPubkey),
			WGPublicKey: hex.Enc(logEntry.WGPublicKey),
			Sequence:    logEntry.Sequence,
			ClientIP:    subnet.ClientIP.String(),
			Timestamp:   logEntry.Timestamp,
			RemoteAddr:  logEntry.RemoteAddr,
		})
	}

	resp := map[string]interface{}{
		"revoked_keys": revokedResp,
		"access_logs":  accessResp,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// deriveWGPublicKey derives a Curve25519 public key from a private key.
func deriveWGPublicKey(privateKey []byte) ([]byte, error) {
	if len(privateKey) != 32 {
		return nil, fmt.Errorf("invalid private key length: %d", len(privateKey))
	}

	// Use wireguard package
	return derivePublicKey(privateKey)
}

// deriveNostrPublicKey derives a secp256k1 public key from a secret key.
func deriveNostrPublicKey(secretKey []byte) ([]byte, error) {
	if len(secretKey) != 32 {
		return nil, fmt.Errorf("invalid secret key length: %d", len(secretKey))
	}

	// Use nostr library's key derivation
	pk, err := deriveSecp256k1PublicKey(secretKey)
	if err != nil {
		return nil, err
	}
	return pk, nil
}
