package peer

import (
	"fmt"
	"strings"
	"time"

	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
	"github.com/biter777/countries"
)

type PeerPerm int

const (
	READ PeerRelation = iota
	WRITE
	MONITOR
)

type PeerRelation int

const (
	NONE PeerRelation = iota
	SELF
	PARTNER
	BLACKLIST
	PENDING_PARTNER
	MASTER
	NANO
	PENDING_NANO
	PENDING_MASTER
)

var path = []string{"known", "self", "partner", "blacklist", "pending_partner", "master", "nano", "pending_nano", "pending_master"}

func GetRelationPath(str string) int {
	for i, p := range path {
		fmt.Println("GetRelationPath", i, p)
		if str == p {
			return i
		}
	}
	return -1
}

func (m PeerRelation) Path() string {
	return path[m]
}

func (m PeerRelation) String() string {
	return strings.ToUpper(path[m])
}

func (m PeerRelation) EnumIndex() int {
	return int(m)
}

// BehaviorWarning records a single misbehavior observed by a trusted service.
type BehaviorWarning struct {
	At          time.Time              `json:"at" bson:"at"`
	ReporterApp string                 `json:"reporter_app" bson:"reporter_app"`
	Severity    tools.BehaviorSeverity `json:"severity" bson:"severity"`
	Reason      string                 `json:"reason" bson:"reason"`
	Evidence    string                 `json:"evidence,omitempty" bson:"evidence,omitempty"`
}

// PeerLocation holds the voluntarily disclosed geographic position of a node.
// Granularity controls how precise the location is:
//
//	0 = not disclosed
//	1 = continent   (±15°)
//	2 = country     (±3°)   — default
//	3 = region      (±0.5°)
//	4 = city        (±0.05°)
//
// The coordinates are always fuzzed by oc-discovery before publication,
// so a granularity-2 location identifies only the rough country area.
type PeerLocation struct {
	Latitude    float64 `json:"latitude" bson:"latitude"`
	Longitude   float64 `json:"longitude" bson:"longitude"`
	Granularity int     `json:"granularity" bson:"granularity"`

	Country  countries.CountryCode `json:"country,omitempty" bson:"country,omitempty"`
	Timezone string                `json:"timezone,omitempty" bson:"timezone,omitempty"`
}

// Peer is a struct that represents a peer
type Peer struct {
	utils.AbstractObject
	IsNano bool `json:"is_nano" bson:"is_nano"`

	PeerPerms []PeerPerm `json:"peer_perms" bson:"peer_perms"`

	RelationLastChangeDate time.Time `json:"relation_last_change_date" bson:"relation_last_change_date"`
	RelationLastChangeUser string    `json:"relation_last_change_user" bson:"relation_last_change_user"`

	Verify         bool   `json:"verify" bson:"verify"`
	OrganizationID string `json:"organization_id" bson:"organization_id"`
	PeerID         string `json:"peer_id" bson:"peer_id" validate:"required"`

	APIUrl          string          `json:"api_url" bson:"api_url" validate:"required"`               // Url is the URL of the peer (base64url)
	StreamAddress   string          `json:"stream_address" bson:"stream_address" validate:"required"` // Url is the URL of the peer (base64url)
	NATSAddress     string          `json:"nats_address" bson:"nats_address" validate:"required"`
	WalletAddress   string          `json:"wallet_address" bson:"wallet_address" validate:"required"` // WalletAddress is the wallet address of the peer
	PublicKey       string          `json:"public_key" bson:"public_key" validate:"required"`         // PublicKey is the public key of the peer
	Relation        PeerRelation    `json:"relation" bson:"relation" default:"0"`
	ServicesState   map[string]int  `json:"services_state,omitempty" bson:"services_state,omitempty"`
	FailedExecution []PeerExecution `json:"failed_execution" bson:"failed_execution"` // FailedExecution is the list of failed executions, to be retried

	// Location is the voluntarily disclosed (and fuzzed) geographic position.
	Location *PeerLocation `json:"location,omitempty" bson:"location,omitempty"`

	// Trust scoring — maintained by oc-discovery from PEER_BEHAVIOR_EVENT reports.
	TrustScore       float64           `json:"trust_score" bson:"trust_score" default:"100"`
	BlacklistReason  string            `json:"blacklist_reason,omitempty" bson:"blacklist_reason,omitempty"`
	BehaviorWarnings []BehaviorWarning `json:"behavior_warnings,omitempty" bson:"behavior_warnings,omitempty"`

	// MasterID is the libp2p PeerID of this peer's MASTER node.
	// Set by a NANO in its own signed PeerRecord so intermediaries cannot forge it.
	// When oc-discovery fails to reach a NANO, it routes the booking to MasterID instead.
	MasterID string `json:"master_id,omitempty" bson:"master_id,omitempty"`

	// Volatile connectivity state — never persisted to DB (bson:"-").
	// Set in-memory by oc-peer when it receives a PEER_OBSERVE_RESPONSE_EVENT.
	// Considered offline when LastHeartbeat is older than 60 s (30 s interval + 30 s grace).
	Online        bool       `json:"online" bson:"-"`
	LastHeartbeat *time.Time `json:"last_heartbeat,omitempty" bson:"-"`
}

func (ri *Peer) Extend(typ ...string) map[string][]tools.DataType {
	ext := ri.AbstractObject.Extend(typ...)
	for _, t := range typ {
		switch t {
		case "peer":
			if _, ok := ext[t]; !ok {
				ext[t] = []tools.DataType{}
			}
			ext[t] = append(ext[t], tools.PEER)
		}
	}
	return ext
}

func (ao *Peer) VerifyAuth(callName string, request *tools.APIRequest) bool {
	return true
}

// BlacklistThreshold is the trust score below which a peer is auto-blacklisted.
const BlacklistThreshold = 20.0

// ApplyBehaviorReport records a misbehavior, deducts the trust penalty, and
// returns true when the trust score has fallen below BlacklistThreshold so the
// caller can trigger the relation change.
func (p *Peer) ApplyBehaviorReport(r tools.PeerBehaviorReport) (shouldBlacklist bool) {
	p.BehaviorWarnings = append(p.BehaviorWarnings, BehaviorWarning{
		At:          r.At,
		ReporterApp: r.ReporterApp,
		Severity:    r.Severity,
		Reason:      r.Reason,
		Evidence:    r.Evidence,
	})
	if p.TrustScore == 0 {
		p.TrustScore = 100 // initialise if never set
	}
	p.TrustScore -= r.Severity.Penalty()
	if p.TrustScore < 0 {
		p.TrustScore = 0
	}
	if p.TrustScore <= BlacklistThreshold {
		p.BlacklistReason = r.Reason
		return true
	}
	return false
}

// ResetTrust clears all behavior history and resets the trust score to 100.
// Must be called when a peer relation is manually set to NONE or PARTNER.
func (p *Peer) ResetTrust() {
	p.TrustScore = 100
	p.BlacklistReason = ""
	p.BehaviorWarnings = nil
}

// AddExecution adds an execution to the list of failed executions
func (ao *Peer) AddExecution(exec PeerExecution) {
	found := false
	for _, v := range ao.FailedExecution { // Check if the execution is already in the list
		if v.Url == exec.Url && v.Method == exec.Method && fmt.Sprint(v.Body) == fmt.Sprint(exec.Body) {
			found = true
			break
		}
	}
	if !found {
		ao.FailedExecution = append(ao.FailedExecution, exec)
	}
}

// RemoveExecution removes an execution from the list of failed executions
func (ao *Peer) RemoveExecution(exec PeerExecution) {
	new := []PeerExecution{}
	for i, v := range ao.FailedExecution {
		if !(v.Url == exec.Url && v.Method == exec.Method && fmt.Sprint(v.Body) == fmt.Sprint(exec.Body)) {
			new = append(new, ao.FailedExecution[i])
		}
	}
	ao.FailedExecution = new
}

// LaunchPeerExecution launches an execution on a peer
func (p *Peer) LaunchPeerExecution(peerID string, dataID string, dt tools.DataType, method tools.METHOD, body interface{}, caller *tools.HTTPCaller) (map[string]interface{}, error) {
	p.UUID = peerID
	return cache.LaunchPeerExecution(peerID, dataID, dt, method, body, caller) // Launch the execution on the peer through the cache
}
func (d *Peer) GetAccessor(request *tools.APIRequest) utils.Accessor {
	data := NewAccessor(request) // Create a new instance of the accessor
	return data
}
