// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later

package db

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"time"

	sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
	"github.com/MeshCore-Beacon/beacon-server/internal/api"
	"github.com/jackc/pgx/v5/pgtype"
)

type tracePayload struct {
	PathHashes []string  `json:"pathHashes"`
	Flags      byte      `json:"flags"`
	SNRValues  []float32 `json:"snrValues"`
}

func (s *Store) UpsertTraceIATA(ctx context.Context, traceTag []byte, iata string, heardAt time.Time) error {
	return s.q.UpsertTraceIATA(ctx, sqlc.UpsertTraceIATAParams{
		TraceTag:  traceTag,
		Iata:      iata,
		LastHeard: pgtype.Timestamptz{Time: heardAt, Valid: true},
	})
}

func (s *Store) DeleteOldTraceIATAs(ctx context.Context, cutoff time.Time) error {
	return s.q.DeleteOldTraceIATAs(ctx, pgtype.Timestamptz{Time: cutoff, Valid: true})
}

func (s *Store) ListTraceTags(ctx context.Context, iatas []string, scope, traceType string, since, until time.Time, cursor time.Time, limit int32) ([]api.TraceTagSummary, error) {
	var sinceTS, untilTS, cursorTS pgtype.Timestamptz
	if !since.IsZero() {
		sinceTS = pgtype.Timestamptz{Time: since, Valid: true}
	}
	if !until.IsZero() {
		untilTS = pgtype.Timestamptz{Time: until, Valid: true}
	}
	if !cursor.IsZero() {
		cursorTS = pgtype.Timestamptz{Time: cursor, Valid: true}
	}
	rows, err := s.q.ListTraceTags(ctx, sqlc.ListTraceTagsParams{
		Column1: iatas,
		Column2: scope,
		Column3: sinceTS,
		Column4: untilTS,
		Column5: cursorTS,
		Limit:   limit,
		Column7: traceType,
	})
	if err != nil {
		return nil, err
	}
	items := make([]api.TraceTagSummary, 0, len(rows))
	for _, r := range rows {
		var best tracePayload
		if len(r.BestPayload) > 0 {
			_ = json.Unmarshal(r.BestPayload, &best)
		}
		items = append(items, api.TraceTagSummary{
			TraceTag:     r.TraceTag,
			FirstHeardAt: r.FirstHeardAt.Time.UnixMilli(),
			LastHeardAt:  r.LastHeardAt.Time.UnixMilli(),
			PacketCount:  r.PacketCount,
			IATACount:    r.IataCount,
			TraceType:    r.TraceType,
			PathHashes:   best.PathHashes,
			SNRValues:    best.SNRValues,
		})
	}
	return items, nil
}

func (s *Store) GetTraceByTag(ctx context.Context, tag string) (*api.TraceDetail, error) {
	rows, err := s.q.GetPacketsByTraceTag(ctx, tag)
	if err != nil {
		return nil, err
	}
	if len(rows) == 0 {
		return nil, nil
	}
	detail := &api.TraceDetail{
		TraceTag: tag,
		Packets:  make([]api.TracePacket, 0, len(rows)),
	}
	for _, r := range rows {
		packet := api.TracePacket{
			PacketHash:    r.PacketHashHex,
			RouteType:     r.RouteType,
			RouteTypeName: api.RouteTypeName(r.RouteType),
			Scope:         r.ScopeName,
			FirstHeardAt:  r.FirstHeardAt.Time.UnixMilli(),
			LastHeardAt:   r.LastHeardAt.Time.UnixMilli(),
		}
		var parsed tracePayload
		if err := json.Unmarshal(r.ParsedPayload, &parsed); err == nil {
			// build raw path
			rawPath := make([]api.RawHop, 0, len(parsed.PathHashes))
			for i, h := range parsed.PathHashes {
				hop := api.RawHop{Hash: h}
				if i < len(parsed.SNRValues) {
					snr := parsed.SNRValues[i]
					hop.SNR = &snr
				}
				rawPath = append(rawPath, hop)
			}
			packet.RawPath = rawPath
		}
		if len(r.Iatas) > 0 {
			packet.ResolvedRoute = s.resolveTraceRoute(ctx, &parsed, r.Iatas)
		}
		detail.Packets = append(detail.Packets, packet)
	}
	return detail, nil
}

func (s *Store) resolveTraceRoute(ctx context.Context, payload *tracePayload, iatas []string) []api.ResolvedHop {
	if payload == nil || len(payload.PathHashes) == 0 {
		return nil
	}
	hashSize := int(1 << (payload.Flags & 0x03))
	hashes := make([][]byte, 0, len(payload.PathHashes))
	for _, h := range payload.PathHashes {
		b, err := hex.DecodeString(h)
		if err == nil {
			hashes = append(hashes, b)
		}
	}
	confidenceRank := map[string]int{"none": 0, "ambiguous": 1, "high": 2}
	type hopResult struct {
		confidence string
		entries    []api.ResolvedPathEntry
	}
	merged := make([]hopResult, len(hashes))
	for i := range merged {
		merged[i] = hopResult{confidence: "none"}
	}
	for _, iata := range iatas {
		resolved, err := s.ResolvePathHashes(ctx, iata, hashes)
		if err != nil {
			continue
		}
		for i, hash := range hashes {
			key := hex.EncodeToString(hash[:hashSize])
			entries := resolved[key]
			var confidence string
			switch len(entries) {
			case 0:
				confidence = "none"
			case 1:
				confidence = "high"
			default:
				confidence = "ambiguous"
			}
			if confidenceRank[confidence] > confidenceRank[merged[i].confidence] {
				merged[i] = hopResult{confidence: confidence, entries: entries}
			}
		}
	}
	route := make([]api.ResolvedHop, 0, len(hashes))
	for i, hr := range merged {
		hop := api.ResolvedHop{
			Confidence: hr.confidence,
			Nodes:      make([]api.ResolvedNode, 0, len(hr.entries)),
		}
		if i < len(payload.SNRValues) {
			snr := payload.SNRValues[i]
			hop.SNR = &snr
		}
		for _, e := range hr.entries {
			hop.Nodes = append(hop.Nodes, api.ResolvedNode{
				ID:        e.NodeID,
				Name:      e.Name,
				Latitude:  e.Latitude,
				Longitude: e.Longitude,
				PublicKey: hex.EncodeToString(e.PublicKey),
			})
		}
		route = append(route, hop)
	}
	return route
}
