package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"strconv"
	"sync"
	"time"

	"spektr/internal/config"
	"spektr/internal/discovery"
	grpcclient "spektr/internal/grpc"
	pb "spektr/internal/grpc/pb"

	"github.com/wailsapp/wails/v2/pkg/runtime"
)

const HardAddr = "192.168.1.10:50051"

type App struct {
	ctx              context.Context
	mu               sync.Mutex
	client           *grpcclient.Client
	currentFrequency string
	connectionStatus string
}

func NewApp() *App {
	return &App{
		currentFrequency: "145",
		connectionStatus: "модуль не подключен",
	}
}

func (a *App) startup(ctx context.Context) {
	a.ctx = ctx

	go func() {
		client, status := connectClient(ctx)
		a.mu.Lock()
		defer a.mu.Unlock()
		a.client = client
		a.connectionStatus = status
	}()
}

func (a *App) Quit() {
	a.mu.Lock()
	client := a.client
	a.client = nil
	a.mu.Unlock()

	if client != nil {
		_ = client.Close()
	}
	runtime.Quit(a.ctx)
}

type DeviceResponse struct {
	Status    string `json:"status"`
	Frequency string `json:"frequency"`
}

func (a *App) CheckConnection() (DeviceResponse, error) {
	log.Println("CheckConnection called")
	start := time.Now()
	log.Println("[TIMING] CheckConnection START")
	resp := DeviceResponse{
		Status:    "модуль не подключен",
		Frequency: a.getFrequency(),
	}

	//клиента вызываю в другом месте теперь
	//client, err := a.getClientOrConnect()
	//if err != nil {
	//	return resp, err
	//}

	//avMsg, err := client.Send(&pb.ClientMessage{
	//	RequestId: requestID(),
	//	Payload:   &pb.ClientMessage_Av{Av: &pb.AvCommand{}},
	//})
	t1 := time.Now()
	avMsg, err := a.sendWithReconnect(func() *pb.ClientMessage {
		return &pb.ClientMessage{
			RequestId: requestID(),
			Payload:   &pb.ClientMessage_Av{Av: &pb.AvCommand{}},
		}
	})
	log.Printf("[TIMING] AV request took: %v", time.Since(t1))
	if err != nil {
		return resp, err
	}
	if errMsg := avMsg.GetError(); errMsg != nil {
		return resp, errors.New(errMsg.GetMessage())
	}
	if av := avMsg.GetAv(); av != nil {
		resp.Status = fmt.Sprintf("HW: %s FW: %s", av.GetHw(), av.GetSw())
	}

	//freqMsg, err := client.Send(&pb.ClientMessage{
	//	RequestId: requestID(),
	//	Payload:   &pb.ClientMessage_GetFrequency{GetFrequency: &pb.GetFrequencyCommand{}},
	//})
	t2 := time.Now()
	freqMsg, err := a.sendWithReconnect(func() *pb.ClientMessage {
		return &pb.ClientMessage{
			RequestId: requestID(),
			Payload:   &pb.ClientMessage_GetFrequency{GetFrequency: &pb.GetFrequencyCommand{}},
		}
	})
	log.Printf("[TIMING] GetFreq request took: %v", time.Since(t2))
	if err != nil {
		return resp, err
	}
	if errMsg := freqMsg.GetError(); errMsg != nil {
		return resp, errors.New(errMsg.GetMessage())
	}
	if freq := freqMsg.GetGetFrequency(); freq != nil {
		resp.Frequency = strconv.FormatUint(uint64(freq.GetFrequency()), 10)
		a.setFrequency(resp.Frequency)
	}

	a.mu.Lock()
	a.connectionStatus = resp.Status
	a.mu.Unlock()
	log.Printf("[TIMING] CheckConnection TOTAL: %v", time.Since(start))
	return resp, nil
}

func (a *App) SetFrequency(freq string) (string, error) {
	val, err := strconv.Atoi(freq)
	if err != nil || val < 70 || val > 8545 {
		//return "", errors.New("вводимое значение должно быть целым положительным числом")
		return "", errors.New(`
		- значение может быть только числом
		- диапазон от 70 до 8545
		`)
	}

	//клиента я вызываю теперь в другом месте
	//client, err := a.getClientOrConnect()
	//if err != nil {
	//	return "", err
	//}

	//resp, err := client.Send(&pb.ClientMessage{
	//	RequestId: requestID(),
	//	Payload: &pb.ClientMessage_SetFrequency{
	//		SetFrequency: &pb.SetFrequencyCommand{Frequency: uint32(val)},
	//	},
	//})
	resp, err := a.sendWithReconnect(func() *pb.ClientMessage {
		return &pb.ClientMessage{
			RequestId: requestID(),
			Payload: &pb.ClientMessage_SetFrequency{
				SetFrequency: &pb.SetFrequencyCommand{Frequency: uint32(val)},
			},
		}
	})
	if err != nil {
		return "", err
	}
	if errMsg := resp.GetError(); errMsg != nil {
		return "", errors.New(errMsg.GetMessage())
	}
	if ack := resp.GetSetFrequency(); ack != nil && !ack.GetOk() {
		return "", errors.New("не удалось установить частоту")
	}

	freqStr := strconv.Itoa(val)
	a.setFrequency(freqStr)
	return freqStr, nil
}

func (a *App) getClient() *grpcclient.Client {
	a.mu.Lock()
	defer a.mu.Unlock()
	return a.client
}

func (a *App) getClientOrConnect() (*grpcclient.Client, error) {
	client := a.getClient()
	if client != nil {
		return client, nil
	}

	ctx := a.ctx
	if ctx == nil {
		ctx = context.Background()
	}

	client, status := connectClient(ctx)
	if client == nil {
		return nil, errors.New(status)
	}

	a.mu.Lock()
	if a.client == nil {
		a.client = client
		a.connectionStatus = status
		a.mu.Unlock()
		return client, nil
	}
	existing := a.client
	a.mu.Unlock()

	_ = client.Close()
	return existing, nil
}

func (a *App) dropClient(client *grpcclient.Client) {
	a.mu.Lock()
	if a.client == client {
		a.client = nil
		a.connectionStatus = "модуль не подключен"
	}
	a.mu.Unlock()

	if client != nil {
		_ = client.Close()
	}
}

func (a *App) sendWithReconnect(build func() *pb.ClientMessage) (*pb.ServerMessage, error) {
	client, err := a.getClientOrConnect()
	if err != nil {
		return nil, err
	}

	resp, err := client.Send(build())
	if err == nil {
		return resp, nil
	}

	// stream умер (часто после рестарта демона) — сбрасываем и пробуем 1 раз заново
	a.dropClient(client)

	client, err = a.getClientOrConnect()
	if err != nil {
		return nil, err
	}

	return client.Send(build())
}

func (a *App) getFrequency() string {
	a.mu.Lock()
	defer a.mu.Unlock()
	return a.currentFrequency
}

func (a *App) setFrequency(freq string) {
	a.mu.Lock()
	a.currentFrequency = freq
	a.mu.Unlock()
}

func connectClient(ctx context.Context) (*grpcclient.Client, string) {
	cfg := config.Default()
	addr := cfg.GRPCAdvertiseAddr

	discoveryCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
	if discovered, err := discovery.Discover(discoveryCtx, cfg.DiscoveryPort); err == nil && discovered != "" {
		addr = discovered
	}
	cancel()

	dialCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	//client, err := grpcclient.Dial(dialCtx, addr)
	client, err := grpcclient.Dial(dialCtx, HardAddr)
	if err != nil {
		log.Printf("gRPC connect failed: %v", err)
		return nil, "gRPC-клиент недоступен"
	}

	log.Printf("connected to gRPC at %s", addr)
	return client, "модуль подключен"
}

func requestID() string {
	return strconv.FormatInt(time.Now().UnixNano(), 10)
}
