package appcontext

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/AFASystems/presence/internal/pkg/model"
	"github.com/redis/go-redis/v9"
	"github.com/segmentio/kafka-go"
)

// AppState provides centralized access to application state
type AppState struct {
	beacons          model.BeaconsList
	httpResults      model.HTTPResultList
	settings         model.Settings
	beaconEvents     model.BeaconEventList
	beaconsLookup    map[string]struct{}
	latestList       model.LatestBeaconsList
	kafkaReadersList model.KafkaReadersList
	kafkaWritersList model.KafkaWritersList
	valkeyDB         *redis.Client
}

// NewAppState creates a new application context AppState with default values
func NewAppState() *AppState {
	return &AppState{
		beacons: model.BeaconsList{
			Beacons: make(map[string]model.Beacon),
		},
		httpResults: model.HTTPResultList{
			Results: make(map[string]model.HTTPResult),
		},
		settings: model.Settings{
			Settings: model.SettingsVal{
				LocationConfidence:   4,
				LastSeenThreshold:    15,
				BeaconMetricSize:     30,
				HASendInterval:       5,
				HASendChangesOnly:    false,
				RSSIEnforceThreshold: false,
				RSSIMinThreshold:     100,
			},
		},
		beaconEvents: model.BeaconEventList{
			Beacons: make(map[string]model.BeaconEvent),
		},
		beaconsLookup: make(map[string]struct{}),
		latestList: model.LatestBeaconsList{
			LatestList: make(map[string]model.Beacon),
		},
		kafkaReadersList: model.KafkaReadersList{
			KafkaReaders: make([]*kafka.Reader, 0),
		},
		kafkaWritersList: model.KafkaWritersList{
			KafkaWriters: make([]*kafka.Writer, 0),
		},
	}
}

func (m *AppState) AddValkeyClient(url string) *redis.Client {
	valkeyDB := redis.NewClient(&redis.Options{
		Addr:     url,
		Password: "",
	})

	m.valkeyDB = valkeyDB
	return valkeyDB
}

func (m *AppState) CleanValkeyClient() {
	fmt.Println("shutdown of valkey client starts")
	if err := m.valkeyDB.Close(); err != nil {
		fmt.Println("Error in shuting down valkey client")
	}

	fmt.Println("Succesfully shutting down valkey client")
}

func (m *AppState) AddKafkaWriter(kafkaUrl, topic string) *kafka.Writer {
	kafkaWriter := &kafka.Writer{
		Addr:         kafka.TCP(kafkaUrl),
		Topic:        topic,
		Balancer:     &kafka.LeastBytes{},
		Async:        false,
		RequiredAcks: kafka.RequireAll,
		BatchSize:    100,
		BatchTimeout: 10 * time.Millisecond,
	}

	m.kafkaWritersList.KafkaWritersLock.Lock()
	m.kafkaWritersList.KafkaWriters = append(m.kafkaWritersList.KafkaWriters, kafkaWriter)
	m.kafkaWritersList.KafkaWritersLock.Unlock()

	return kafkaWriter
}

func (m *AppState) CleanKafkaWriters() {
	fmt.Println("shutdown of kafka readers starts")
	for _, r := range m.kafkaWritersList.KafkaWriters {
		if err := r.Close(); err != nil {
			fmt.Printf("Error in closing kafka writer %v", err)
		}
	}

	fmt.Println("Kafka writers graceful shutdown complete")
}

func (m *AppState) AddKafkaReader(kafkaUrl, topic, groupID string) *kafka.Reader {
	brokers := strings.Split(kafkaUrl, ",")
	kafkaReader := kafka.NewReader(kafka.ReaderConfig{
		Brokers:  brokers,
		GroupID:  groupID,
		Topic:    topic,
		MinBytes: 1,
		MaxBytes: 10e6,
	})

	m.kafkaReadersList.KafkaReadersLock.Lock()
	m.kafkaReadersList.KafkaReaders = append(m.kafkaReadersList.KafkaReaders, kafkaReader)
	m.kafkaReadersList.KafkaReadersLock.Unlock()

	return kafkaReader
}

func (m *AppState) CleanKafkaReaders() {
	for _, r := range m.kafkaReadersList.KafkaReaders {
		if err := r.Close(); err != nil {
			fmt.Printf("Error in closing kafka reader %v", err)
		}
	}

	fmt.Println("Kafka readers graceful shutdown complete")
}

// GetBeacons returns thread-safe access to beacons list
func (m *AppState) GetBeacons() *model.BeaconsList {
	return &m.beacons
}

// GetSettings returns thread-safe access to settings
func (m *AppState) GetSettings() *model.Settings {
	return &m.settings
}

// GetBeaconEvents returns thread-safe access to beacon events
func (m *AppState) GetBeaconEvents() *model.BeaconEventList {
	return &m.beaconEvents
}

// GetBeaconsLookup returns thread-safe access to beacon lookup map
func (m *AppState) GetBeaconsLookup() map[string]struct{} {
	return m.beaconsLookup
}

// GetLatestList returns thread-safe access to latest beacons list
func (m *AppState) GetLatestList() *model.LatestBeaconsList {
	return &m.latestList
}

// AddBeaconToLookup adds a beacon ID to the lookup map
func (m *AppState) AddBeaconToLookup(id string) {
	m.beaconsLookup[id] = struct{}{}
}

// RemoveBeaconFromLookup removes a beacon ID from the lookup map
func (m *AppState) RemoveBeaconFromLookup(id string) {
	delete(m.beaconsLookup, id)
}

func (m *AppState) RemoveBeacon(id string) {
	m.beacons.Lock.Lock()
	delete(m.beacons.Beacons, id)
	m.beacons.Lock.Unlock()
}

func (m *AppState) RemoveHTTPResult(id string) {
	m.httpResults.Lock.Lock()
	delete(m.httpResults.Results, id)
	m.httpResults.Lock.Unlock()
}

// BeaconExists checks if a beacon exists in the lookup
func (m *AppState) BeaconExists(id string) bool {
	_, exists := m.beaconsLookup[id]
	return exists
}

// GetBeacon returns a beacon by ID (thread-safe)
func (m *AppState) GetBeacon(id string) (model.Beacon, bool) {
	m.beacons.Lock.RLock()
	defer m.beacons.Lock.RUnlock()

	beacon, exists := m.beacons.Beacons[id]
	return beacon, exists
}

// GetHTTPResult returns a beacon from HTTP results by ID (thread-safe)
func (m *AppState) GetHTTPResult(id string) (model.HTTPResult, bool) {
	m.httpResults.Lock.RLock()
	defer m.httpResults.Lock.RUnlock()

	beacon, exists := m.httpResults.Results[id]
	return beacon, exists
}

// UpdateHTTPResult updates a beacon in the list (thread-safe)
func (m *AppState) UpdateHTTPResult(id string, beacon model.HTTPResult) {
	m.httpResults.Lock.Lock()
	defer m.httpResults.Lock.Unlock()

	m.httpResults.Results[id] = beacon
}

// UpdateBeacon updates a beacon in the list (thread-safe)
func (m *AppState) UpdateBeacon(id string, beacon model.Beacon) {
	m.beacons.Lock.Lock()
	defer m.beacons.Lock.Unlock()

	m.beacons.Beacons[id] = beacon
}

// GetBeaconEvent returns a beacon event by ID (thread-safe)
func (m *AppState) GetBeaconEvent(id string) (model.BeaconEvent, bool) {
	m.beaconEvents.Lock.RLock()
	defer m.beaconEvents.Lock.RUnlock()

	event, exists := m.beaconEvents.Beacons[id]
	return event, exists
}

// UpdateBeaconEvent updates a beacon event in the list (thread-safe)
func (m *AppState) UpdateBeaconEvent(id string, event model.BeaconEvent) {
	m.beaconEvents.Lock.Lock()
	defer m.beaconEvents.Lock.Unlock()

	m.beaconEvents.Beacons[id] = event
}

// GetLatestBeacon returns the latest beacon by ID (thread-safe)
func (m *AppState) GetLatestBeacon(id string) (model.Beacon, bool) {
	m.latestList.Lock.RLock()
	defer m.latestList.Lock.RUnlock()

	beacon, exists := m.latestList.LatestList[id]
	return beacon, exists
}

// UpdateLatestBeacon updates the latest beacon in the list (thread-safe)
func (m *AppState) UpdateLatestBeacon(id string, beacon model.Beacon) {
	m.latestList.Lock.Lock()
	defer m.latestList.Lock.Unlock()

	m.latestList.LatestList[id] = beacon
}

// GetAllBeacons returns a copy of all beacons
func (m *AppState) GetAllBeacons() map[string]model.Beacon {
	m.beacons.Lock.RLock()
	defer m.beacons.Lock.RUnlock()

	beacons := make(map[string]model.Beacon)
	for id, beacon := range m.beacons.Beacons {
		beacons[id] = beacon
	}
	return beacons
}

// GetAllHttpResults returns a copy of all beacons
func (m *AppState) GetAllHttpResults() map[string]model.HTTPResult {
	m.httpResults.Lock.RLock()
	defer m.httpResults.Lock.RUnlock()

	beacons := make(map[string]model.HTTPResult)
	for id, beacon := range m.httpResults.Results {
		beacons[id] = beacon
	}
	return beacons
}

// GetAllLatestBeacons returns a copy of all latest beacons
func (m *AppState) GetAllLatestBeacons() map[string]model.Beacon {
	m.latestList.Lock.RLock()
	defer m.latestList.Lock.RUnlock()

	beacons := make(map[string]model.Beacon)
	for id, beacon := range m.latestList.LatestList {
		beacons[id] = beacon
	}
	return beacons
}

// GetBeaconCount returns the number of tracked beacons
func (m *AppState) GetBeaconCount() int {
	m.beacons.Lock.RLock()
	defer m.beacons.Lock.RUnlock()

	return len(m.beacons.Beacons)
}

// GetSettingsValue returns current settings as a value
func (m *AppState) GetSettingsValue() model.SettingsVal {
	m.settings.Lock.RLock()
	defer m.settings.Lock.RUnlock()

	return m.settings.Settings
}

// UpdateSettings updates the system settings (thread-safe)
func (m *AppState) UpdateSettings(newSettings model.SettingsVal) {
	m.settings.Lock.Lock()
	defer m.settings.Lock.Unlock()

	m.settings.Settings = newSettings
}

func (m *AppState) PersistSettings(client *redis.Client, ctx context.Context) {
	d, err := json.Marshal(m.GetSettingsValue())
	if err != nil {
		fmt.Printf("Error in marshalling settings: %v", err)
		return
	}

	if err := client.Set(ctx, "settings", d, 0).Err(); err != nil {
		fmt.Printf("Error in persisting settings: %v", err)
	}
}
