package stream

import (
	"context"
	"crypto/subtle"
	"encoding/json"
	"errors"
	"fmt"
	"oc-discovery/daemons/node/common"

	oclib "cloud.o-forge.io/core/oc-lib"
	"cloud.o-forge.io/core/oc-lib/dbs"
	"cloud.o-forge.io/core/oc-lib/models/booking/planner"
	"cloud.o-forge.io/core/oc-lib/models/peer"
	"cloud.o-forge.io/core/oc-lib/models/resources"
	"cloud.o-forge.io/core/oc-lib/tools"
	"github.com/libp2p/go-libp2p/core/network"
	pp "github.com/libp2p/go-libp2p/core/peer"
)

type Verify struct {
	IsVerified bool `json:"is_verified"`
}

func (ps *StreamService) handleEvent(protocol string, evt *common.Event, s network.Stream) error {
	fmt.Println("handleEvent", protocol)
	if protocol == ProtocolObserve {
		// Distinguish between an open request and a close request by inspecting
		// the ObserveRequest payload. The remote wraps both in a common.Event
		// with Type=ProtocolObserve so the persistent readLoop can decode them.
		var req ObserveRequest
		if evt.Payload != nil {
			json.Unmarshal(evt.Payload, &req) //nolint:errcheck — zero value means open
		}
		if req.Close {
			ps.observeCache.cancel(s.Conn().RemotePeer().String())
			return nil
		}
		return ps.handleIncomingObserve(s)
	}
	if protocol == observeHBEventType {
		return ps.handleObserveHeartbeat(evt)
	}

	ps.handleEventFromPartner(evt, protocol)
	/*if protocol == ProtocolVerifyResource {
		if evt.DataType == -1 {
			tools.NewNATSCaller().SetNATSPub(tools.VERIFY_RESOURCE, tools.NATSResponse{
				FromApp: "oc-discovery",
				Method:  int(tools.VERIFY_RESOURCE),
				Payload: evt.Payload,
			})
		} else if err := ps.verifyResponse(evt); err != nil {
			return err
		}
	}*/
	if protocol == ProtocolSendPlanner {
		fmt.Println("sendPlanner", evt)
		if err := ps.sendPlanner(evt); err != nil {
			return err
		}
	}
	if protocol == ProtocolSearchResource && evt.DataType > -1 {
		if err := ps.retrieveResponse(evt); err != nil {
			return err
		}
	}
	if protocol == ProtocolConsidersResource {
		if err := ps.pass(evt, tools.CONSIDERS_EVENT); err != nil {
			return err
		}
	}
	if protocol == ProtocolSourcePresignResource {
		if err := ps.pass(evt, tools.SOURCE_PRESIGN_EVENT); err != nil {
			return err
		}
	}
	if protocol == ProtocolAdmiraltyConfigResource {
		if err := ps.pass(evt, tools.ADMIRALTY_CONFIG_EVENT); err != nil {
			return err
		}
	}
	if protocol == ProtocolMinioConfigResource {
		if err := ps.pass(evt, tools.MINIO_CONFIG_EVENT); err != nil {
			return err
		}
	}
	return errors.New("no action authorized available : " + protocol)
}

func (abs *StreamService) verifyResponse(event *common.Event) error { //
	res, err := resources.ToResource(int(event.DataType), event.Payload)
	if err != nil || res == nil {
		return nil
	}
	verify := Verify{
		IsVerified: false,
	}
	access := oclib.NewRequestAdmin(oclib.LibDataEnum(event.DataType), nil)
	data := access.LoadOne(res.GetID())
	if data.Err == "" && data.Data != nil {
		if b, err := json.Marshal(data.Data); err == nil {
			if res2, err := resources.ToResource(int(event.DataType), b); err == nil {
				verify.IsVerified = subtle.ConstantTimeCompare(res.GetSignature(), res2.GetSignature()) == 1
			}
		}
	}
	if b, err := json.Marshal(verify); err == nil {
		abs.PublishCommon(nil, event.User, event.Groups, event.From, ProtocolVerifyResource, b)
	}
	return nil
}

func (abs *StreamService) sendPlanner(event *common.Event) error { //
	fmt.Println("sendPlanner", len(event.Payload))
	if len(event.Payload) == 0 {
		if plan, err := planner.GenerateShallow(&tools.APIRequest{Admin: true}); err == nil {
			if b, err := json.Marshal(plan); err == nil {
				abs.PublishCommon(nil, event.User, event.Groups, event.From, ProtocolSendPlanner, b)
			} else {
				return err
			}
		} else {
			return err
		}
	} else { // if not empty so it's
		m := map[string]interface{}{}
		if err := json.Unmarshal(event.Payload, &m); err == nil {
			m["peer_id"] = event.From
			if pl, err := json.Marshal(m); err == nil {
				go tools.NewNATSCaller().SetNATSPub(tools.PLANNER_EXECUTION, tools.NATSResponse{
					FromApp:  "oc-discovery",
					Datatype: tools.DataType(oclib.BOOKING),
					Method:   int(tools.PLANNER_EXECUTION),
					Payload:  pl,
				})
			}
		}
	}
	return nil
}

func (abs *StreamService) retrieveResponse(event *common.Event) error { //
	/*if !abs.ResourceSearches.IsActive(event.User) {
		return nil // search already closed or timed out
	}*/
	res, err := resources.ToResource(int(event.DataType), event.Payload)
	if err != nil || res == nil {
		return nil
	}
	// A response arrived — reset the idle timeout.
	abs.ResourceSearches.ResetIdle(event.User)
	b, err := json.Marshal(res.Serialize(res))
	go tools.NewNATSCaller().SetNATSPub(tools.SEARCH_EVENT, tools.NATSResponse{
		FromApp:  "oc-discovery",
		User:     event.User,
		Datatype: tools.DataType(event.DataType),
		Method:   int(tools.SEARCH_EVENT),
		Payload:  b,
	})
	return nil
}

func (abs *StreamService) pass(event *common.Event, method tools.NATSMethod) error { //
	go tools.NewNATSCaller().SetNATSPub(method, tools.NATSResponse{
		FromApp:  "oc-discovery",
		User:     event.User,
		Datatype: tools.DataType(event.DataType),
		Method:   int(method),
		Payload:  event.Payload,
	})
	return nil
}

// resolveBookingNano does a single DB lookup and returns:
//
//	(nil, true)  — not a booking, dest_peer_id absent, or dest == self → process normally, no forward
//	(nano, true) — dest is one of our NANO peers → process + forward to nano
//	(nil, false) — dest is unknown → ignore
func (ps *StreamService) resolveBookingNano(evt *common.Event) (*peer.Peer, bool) {
	if tools.DataType(evt.DataType) != tools.BOOKING {
		return nil, true
	}
	var b struct {
		DestPeerID string `json:"dest_peer_id"`
	}
	if err := json.Unmarshal(evt.Payload, &b); err != nil || b.DestPeerID == "" {
		return nil, true
	}
	if self, err := oclib.GetMySelf(); err == nil && self != nil && b.DestPeerID == self.GetID() {
		return nil, true
	}
	d := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil).Search(&dbs.Filters{
		And: map[string][]dbs.Filter{
			"id":       {{Operator: dbs.EQUAL.String(), Value: b.DestPeerID}},
			"relation": {{Operator: dbs.EQUAL.String(), Value: peer.NANO}},
		},
	}, "", false, 0, 1)
	if len(d.Data) == 0 {
		return nil, false
	}
	return d.Data[0].(*peer.Peer), true
}

func (ps *StreamService) handleEventFromPartner(evt *common.Event, protocol string) error {
	switch protocol {
	case ProtocolSearchResource:
		m := map[string]interface{}{}
		err := json.Unmarshal(evt.Payload, &m)
		if err != nil {
			return err
		}
		if search, ok := m["search"]; ok {
			access := oclib.NewRequestAdmin(oclib.LibDataEnum(oclib.PEER), nil)
			peers := access.Search(&dbs.Filters{
				And: map[string][]dbs.Filter{
					"peer_id": {{Operator: dbs.EQUAL.String(), Value: evt.From}},
				},
			}, evt.From, false, 0, 1)
			if len(peers.Data) > 0 {
				p := peers.Data[0].(*peer.Peer)
				ps.SendResponse(p, evt, fmt.Sprintf("%v", search))
			} else if p, err := ps.Node.GetPeerRecord(context.Background(), evt.From); err == nil && len(p) > 0 { // peer from is peerID
				ps.SendResponse(p[0], evt, fmt.Sprintf("%v", search))
			}
		} else {
			fmt.Println("SEND SEARCH_EVENT SetNATSPub", m, evt.DataType, evt.User)
			tools.NewNATSCaller().SetNATSPub(tools.SEARCH_EVENT, tools.NATSResponse{
				FromApp:  "oc-discovery",
				User:     evt.User,
				Datatype: tools.DataType(evt.DataType),
				Method:   int(tools.SEARCH_EVENT),
				Payload:  evt.Payload,
			})
		}
	case ProtocolCreateResource, ProtocolUpdateResource:
		fmt.Println("RECEIVED Protocol.Update", string(evt.Payload))
		nano, ok := ps.resolveBookingNano(evt)
		if !ok {
			return nil
		}
		tools.NewNATSCaller().SetNATSPub(tools.CREATE_RESOURCE, tools.NATSResponse{
			FromApp:  "oc-discovery",
			User:     evt.User,
			Datatype: tools.DataType(evt.DataType),
			Method:   int(tools.CREATE_RESOURCE),
			Payload:  evt.Payload,
		})
		if nano != nil {
			ps.forwardToNano(nano, evt, protocol)
		}
	case ProtocolDeleteResource:
		nano, ok := ps.resolveBookingNano(evt)
		if !ok {
			return nil
		}
		tools.NewNATSCaller().SetNATSPub(tools.REMOVE_RESOURCE, tools.NATSResponse{
			FromApp:  "oc-discovery",
			User:     evt.User,
			Datatype: tools.DataType(evt.DataType),
			Method:   int(tools.REMOVE_RESOURCE),
			Payload:  evt.Payload,
		})
		if nano != nil {
			ps.forwardToNano(nano, evt, protocol)
		}
	default:
		return errors.New("no action authorized available : " + protocol)
	}
	return nil
}

func (abs *StreamService) SendResponse(p *peer.Peer, event *common.Event, search string) error {
	dts := []tools.DataType{tools.DataType(event.DataType)}
	if event.DataType == -1 { // expect all resources
		dts = []tools.DataType{
			tools.COMPUTE_RESOURCE,
			tools.STORAGE_RESOURCE,
			tools.PROCESSING_RESOURCE,
			tools.DATA_RESOURCE,
			tools.WORKFLOW_RESOURCE,
		}
	}
	if self, err := oclib.GetMySelf(); err != nil {
		return err
	} else {
		for _, dt := range dts {
			access := oclib.NewRequestAdmin(oclib.LibDataEnum(dt), nil)
			searched := access.Search(abs.FilterPeer(self.GetID(), event.Groups, search), "", false, 0, 0)
			for _, ss := range searched.Data {
				// SendResponse uses an admin request so SetAllowedInstances
				// never calls FilterExploitationAuthorizations.  Apply it
				// explicitly here so we never leak private AEs to a remote peer.
				if r, ok := ss.(resources.ResourceInterface); ok {
					r.SetAllowedInstances(&tools.APIRequest{PeerID: p.UUID, Groups: event.Groups, Username: event.User})
				}
				if j, err := json.Marshal(ss); err == nil {
					abs.PublishCommon(&dt, event.User, event.Groups, p.PeerID, ProtocolSearchResource, j)
				}
			}
		}
	}
	// Close the ProtocolSearchResource stream to the requester immediately after
	// sending all results. This prevents TempStream from reusing a stale (already
	// closed by the remote) stream entry for a subsequent search from the same peer,
	// which would cause write failure and no results for the second search.
	if decodedID, err := pp.Decode(p.PeerID); err == nil {
		abs.Mu.Lock()
		if abs.Streams[ProtocolSearchResource] != nil {
			if s, ok := abs.Streams[ProtocolSearchResource][decodedID]; ok {
				s.Stream.Reset()
				delete(abs.Streams[ProtocolSearchResource], decodedID)
			}
		}
		abs.Mu.Unlock()
	}
	return nil
}
