package network

import (
	"fmt"
	"sync"
	"time"

	"github.com/AvengeMedia/danklinux/internal/log"
)

func NewManager() (*Manager, error) {
	detection, err := DetectNetworkStack()
	if err != nil {
		return nil, fmt.Errorf("failed to detect network stack: %w", err)
	}

	log.Infof("Network backend detection: %s", detection.ChosenReason)

	var backend Backend
	switch detection.Backend {
	case BackendNetworkManager:
		nm, err := NewNetworkManagerBackend()
		if err != nil {
			return nil, fmt.Errorf("failed to create NetworkManager backend: %w", err)
		}
		backend = nm

	case BackendIwd:
		iwd, err := NewIWDBackend()
		if err != nil {
			return nil, fmt.Errorf("failed to create iwd backend: %w", err)
		}
		backend = iwd

	case BackendNetworkd:
		if detection.HasIwd && !detection.HasNM {
			wifi, err := NewIWDBackend()
			if err != nil {
				return nil, fmt.Errorf("failed to create iwd backend: %w", err)
			}
			l3, err := NewSystemdNetworkdBackend()
			if err != nil {
				return nil, fmt.Errorf("failed to create networkd backend: %w", err)
			}
			hybrid, err := NewHybridIwdNetworkdBackend(wifi, l3)
			if err != nil {
				return nil, fmt.Errorf("failed to create hybrid backend: %w", err)
			}
			backend = hybrid
		} else {
			nd, err := NewSystemdNetworkdBackend()
			if err != nil {
				return nil, fmt.Errorf("failed to create networkd backend: %w", err)
			}
			backend = nd
		}

	default:
		return nil, fmt.Errorf("no supported network backend found: %s", detection.ChosenReason)
	}

	m := &Manager{
		backend: backend,
		state: &NetworkState{
			NetworkStatus: StatusDisconnected,
			Preference:    PreferenceAuto,
			WiFiNetworks:  []WiFiNetwork{},
		},
		stateMutex:            sync.RWMutex{},
		subscribers:           make(map[string]chan NetworkState),
		subMutex:              sync.RWMutex{},
		stopChan:              make(chan struct{}),
		dirty:                 make(chan struct{}, 1),
		credentialSubscribers: make(map[string]chan CredentialPrompt),
		credSubMutex:          sync.RWMutex{},
	}

	broker := NewSubscriptionBroker(m.broadcastCredentialPrompt)
	if err := backend.SetPromptBroker(broker); err != nil {
		return nil, fmt.Errorf("failed to set prompt broker: %w", err)
	}

	if err := backend.Initialize(); err != nil {
		return nil, fmt.Errorf("failed to initialize backend: %w", err)
	}

	if err := m.syncStateFromBackend(); err != nil {
		return nil, fmt.Errorf("failed to sync initial state: %w", err)
	}

	m.notifierWg.Add(1)
	go m.notifier()

	if err := backend.StartMonitoring(m.onBackendStateChange); err != nil {
		m.Close()
		return nil, fmt.Errorf("failed to start monitoring: %w", err)
	}

	return m, nil
}

func (m *Manager) syncStateFromBackend() error {
	backendState, err := m.backend.GetCurrentState()
	if err != nil {
		return err
	}

	m.stateMutex.Lock()
	m.state.Backend = backendState.Backend
	m.state.NetworkStatus = backendState.NetworkStatus
	m.state.EthernetIP = backendState.EthernetIP
	m.state.EthernetDevice = backendState.EthernetDevice
	m.state.EthernetConnected = backendState.EthernetConnected
	m.state.EthernetConnectionUuid = backendState.EthernetConnectionUuid
	m.state.WiFiIP = backendState.WiFiIP
	m.state.WiFiDevice = backendState.WiFiDevice
	m.state.WiFiConnected = backendState.WiFiConnected
	m.state.WiFiEnabled = backendState.WiFiEnabled
	m.state.WiFiSSID = backendState.WiFiSSID
	m.state.WiFiBSSID = backendState.WiFiBSSID
	m.state.WiFiSignal = backendState.WiFiSignal
	m.state.WiFiNetworks = backendState.WiFiNetworks
	m.state.WiredConnections = backendState.WiredConnections
	m.state.VPNProfiles = backendState.VPNProfiles
	m.state.VPNActive = backendState.VPNActive
	m.state.IsConnecting = backendState.IsConnecting
	m.state.ConnectingSSID = backendState.ConnectingSSID
	m.state.LastError = backendState.LastError
	m.stateMutex.Unlock()

	return nil
}

func (m *Manager) onBackendStateChange() {
	if err := m.syncStateFromBackend(); err != nil {
		log.Errorf("failed to sync state from backend: %v", err)
	}
	m.notifySubscribers()
}

func signalChangeSignificant(old, new uint8) bool {
	if old == 0 || new == 0 {
		return true
	}
	diff := int(new) - int(old)
	if diff < 0 {
		diff = -diff
	}
	return diff >= 5
}

func (m *Manager) snapshotState() NetworkState {
	m.stateMutex.RLock()
	defer m.stateMutex.RUnlock()
	s := *m.state
	s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
	s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
	s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
	s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
	return s
}

func stateChangedMeaningfully(old, new *NetworkState) bool {
	if old.NetworkStatus != new.NetworkStatus {
		return true
	}
	if old.Preference != new.Preference {
		return true
	}
	if old.EthernetConnected != new.EthernetConnected {
		return true
	}
	if old.EthernetIP != new.EthernetIP {
		return true
	}
	if old.WiFiConnected != new.WiFiConnected {
		return true
	}
	if old.WiFiEnabled != new.WiFiEnabled {
		return true
	}
	if old.WiFiSSID != new.WiFiSSID {
		return true
	}
	if old.WiFiBSSID != new.WiFiBSSID {
		return true
	}
	if old.WiFiIP != new.WiFiIP {
		return true
	}
	if !signalChangeSignificant(old.WiFiSignal, new.WiFiSignal) {
		if old.WiFiSignal != new.WiFiSignal {
			return false
		}
	} else if old.WiFiSignal != new.WiFiSignal {
		return true
	}
	if old.IsConnecting != new.IsConnecting {
		return true
	}
	if old.ConnectingSSID != new.ConnectingSSID {
		return true
	}
	if old.LastError != new.LastError {
		return true
	}
	if len(old.WiFiNetworks) != len(new.WiFiNetworks) {
		return true
	}
	if len(old.WiredConnections) != len(new.WiredConnections) {
		return true
	}

	for i := range old.WiFiNetworks {
		oldNet := &old.WiFiNetworks[i]
		newNet := &new.WiFiNetworks[i]
		if oldNet.SSID != newNet.SSID {
			return true
		}
		if oldNet.Connected != newNet.Connected {
			return true
		}
		if oldNet.Saved != newNet.Saved {
			return true
		}
		if oldNet.Autoconnect != newNet.Autoconnect {
			return true
		}
	}

	for i := range old.WiredConnections {
		oldNet := &old.WiredConnections[i]
		newNet := &new.WiredConnections[i]
		if oldNet.ID != newNet.ID {
			return true
		}
		if oldNet.IsActive != newNet.IsActive {
			return true
		}
	}

	// Check VPN profiles count
	if len(old.VPNProfiles) != len(new.VPNProfiles) {
		return true
	}

	// Check active VPN connections count or state
	if len(old.VPNActive) != len(new.VPNActive) {
		return true
	}

	// Check if any active VPN changed
	for i := range old.VPNActive {
		oldVPN := &old.VPNActive[i]
		newVPN := &new.VPNActive[i]
		if oldVPN.UUID != newVPN.UUID {
			return true
		}
		if oldVPN.State != newVPN.State {
			return true
		}
	}

	return false
}

func (m *Manager) GetState() NetworkState {
	return m.snapshotState()
}

func (m *Manager) Subscribe(id string) chan NetworkState {
	ch := make(chan NetworkState, 64)
	m.subMutex.Lock()
	m.subscribers[id] = ch
	m.subMutex.Unlock()
	return ch
}

func (m *Manager) Unsubscribe(id string) {
	m.subMutex.Lock()
	if ch, ok := m.subscribers[id]; ok {
		close(ch)
		delete(m.subscribers, id)
	}
	m.subMutex.Unlock()
}

func (m *Manager) SubscribeCredentials(id string) chan CredentialPrompt {
	ch := make(chan CredentialPrompt, 16)
	m.credSubMutex.Lock()
	m.credentialSubscribers[id] = ch
	m.credSubMutex.Unlock()
	return ch
}

func (m *Manager) UnsubscribeCredentials(id string) {
	m.credSubMutex.Lock()
	if ch, ok := m.credentialSubscribers[id]; ok {
		close(ch)
		delete(m.credentialSubscribers, id)
	}
	m.credSubMutex.Unlock()
}

func (m *Manager) broadcastCredentialPrompt(prompt CredentialPrompt) {
	m.credSubMutex.RLock()
	defer m.credSubMutex.RUnlock()

	for _, ch := range m.credentialSubscribers {
		select {
		case ch <- prompt:
		default:
		}
	}
}

func (m *Manager) notifier() {
	defer m.notifierWg.Done()
	const minGap = 100 * time.Millisecond
	timer := time.NewTimer(minGap)
	timer.Stop()
	var pending bool
	for {
		select {
		case <-m.stopChan:
			timer.Stop()
			return
		case <-m.dirty:
			if pending {
				continue
			}
			pending = true
			timer.Reset(minGap)
		case <-timer.C:
			if !pending {
				continue
			}
			m.subMutex.RLock()
			if len(m.subscribers) == 0 {
				m.subMutex.RUnlock()
				pending = false
				continue
			}

			currentState := m.snapshotState()

			if m.lastNotifiedState != nil && !stateChangedMeaningfully(m.lastNotifiedState, &currentState) {
				m.subMutex.RUnlock()
				pending = false
				continue
			}

			for _, ch := range m.subscribers {
				select {
				case ch <- currentState:
				default:
				}
			}
			m.subMutex.RUnlock()

			stateCopy := currentState
			m.lastNotifiedState = &stateCopy
			pending = false
		}
	}
}

func (m *Manager) notifySubscribers() {
	select {
	case m.dirty <- struct{}{}:
	default:
	}
}

func (m *Manager) SetPromptBroker(broker PromptBroker) error {
	return m.backend.SetPromptBroker(broker)
}

func (m *Manager) SubmitCredentials(token string, secrets map[string]string, save bool) error {
	return m.backend.SubmitCredentials(token, secrets, save)
}

func (m *Manager) CancelCredentials(token string) error {
	return m.backend.CancelCredentials(token)
}

func (m *Manager) GetPromptBroker() PromptBroker {
	return m.backend.GetPromptBroker()
}

func (m *Manager) Close() {
	close(m.stopChan)
	m.notifierWg.Wait()

	if m.backend != nil {
		m.backend.Close()
	}

	m.subMutex.Lock()
	for _, ch := range m.subscribers {
		close(ch)
	}
	m.subscribers = make(map[string]chan NetworkState)
	m.subMutex.Unlock()
}

func (m *Manager) ScanWiFi() error {
	return m.backend.ScanWiFi()
}

func (m *Manager) GetWiFiNetworks() []WiFiNetwork {
	m.stateMutex.RLock()
	defer m.stateMutex.RUnlock()
	networks := make([]WiFiNetwork, len(m.state.WiFiNetworks))
	copy(networks, m.state.WiFiNetworks)
	return networks
}

func (m *Manager) GetNetworkInfo(ssid string) (*WiFiNetwork, error) {
	m.stateMutex.RLock()
	defer m.stateMutex.RUnlock()

	for _, network := range m.state.WiFiNetworks {
		if network.SSID == ssid {
			return &network, nil
		}
	}

	return nil, fmt.Errorf("network not found: %s", ssid)
}

func (m *Manager) GetNetworkInfoDetailed(ssid string) (*NetworkInfoResponse, error) {
	return m.backend.GetWiFiNetworkDetails(ssid)
}

func (m *Manager) ToggleWiFi() error {
	enabled, err := m.backend.GetWiFiEnabled()
	if err != nil {
		return fmt.Errorf("failed to get WiFi state: %w", err)
	}

	err = m.backend.SetWiFiEnabled(!enabled)
	if err != nil {
		return fmt.Errorf("failed to toggle WiFi: %w", err)
	}

	return nil
}

func (m *Manager) EnableWiFi() error {
	err := m.backend.SetWiFiEnabled(true)
	if err != nil {
		return fmt.Errorf("failed to enable WiFi: %w", err)
	}

	return nil
}

func (m *Manager) DisableWiFi() error {
	err := m.backend.SetWiFiEnabled(false)
	if err != nil {
		return fmt.Errorf("failed to disable WiFi: %w", err)
	}

	return nil
}

func (m *Manager) ConnectWiFi(req ConnectionRequest) error {
	return m.backend.ConnectWiFi(req)
}

func (m *Manager) DisconnectWiFi() error {
	return m.backend.DisconnectWiFi()
}

func (m *Manager) ForgetWiFiNetwork(ssid string) error {
	return m.backend.ForgetWiFiNetwork(ssid)
}

func (m *Manager) GetWiredConfigs() []WiredConnection {
	m.stateMutex.RLock()
	defer m.stateMutex.RUnlock()
	configs := make([]WiredConnection, len(m.state.WiredConnections))
	copy(configs, m.state.WiredConnections)
	return configs
}

func (m *Manager) GetWiredNetworkInfoDetailed(uuid string) (*WiredNetworkInfoResponse, error) {
	return m.backend.GetWiredNetworkDetails(uuid)
}

func (m *Manager) ConnectEthernet() error {
	return m.backend.ConnectEthernet()
}

func (m *Manager) DisconnectEthernet() error {
	return m.backend.DisconnectEthernet()
}

func (m *Manager) activateConnection(uuid string) error {
	return m.backend.ActivateWiredConnection(uuid)
}

func (m *Manager) ListVPNProfiles() ([]VPNProfile, error) {
	return m.backend.ListVPNProfiles()
}

func (m *Manager) ListActiveVPN() ([]VPNActive, error) {
	return m.backend.ListActiveVPN()
}

func (m *Manager) ConnectVPN(uuidOrName string, singleActive bool) error {
	return m.backend.ConnectVPN(uuidOrName, singleActive)
}

func (m *Manager) DisconnectVPN(uuidOrName string) error {
	return m.backend.DisconnectVPN(uuidOrName)
}

func (m *Manager) DisconnectAllVPN() error {
	return m.backend.DisconnectAllVPN()
}

func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
	return m.backend.ClearVPNCredentials(uuidOrName)
}

func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
	return m.backend.SetWiFiAutoconnect(ssid, autoconnect)
}
