package state

import (
	"errors"
	"math/big"

	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

var errPalamInvalidStackItem = errors.New("invalid stack item")

// FlowStatus represents the status of a transaction flow.
type FlowStatus uint8

const (
	FlowStatusActive    FlowStatus = 0
	FlowStatusArchived  FlowStatus = 1
	FlowStatusDisputed  FlowStatus = 2
)

// DeclassifyStatus represents the status of a declassification request.
type DeclassifyStatus uint8

const (
	DeclassifyPending  DeclassifyStatus = 0
	DeclassifyApproved DeclassifyStatus = 1
	DeclassifyDenied   DeclassifyStatus = 2
	DeclassifyExpired  DeclassifyStatus = 3
)

// PalamRole represents roles for transparency access.
type PalamRole uint8

const (
	PalamRoleConsumer    PalamRole = 0
	PalamRoleMerchant    PalamRole = 1
	PalamRoleDistributor PalamRole = 2
	PalamRoleProducer    PalamRole = 3
	PalamRoleNGO         PalamRole = 4
	PalamRoleAuditor     PalamRole = 5
)

// AccessType represents types of data access.
type AccessType uint8

const (
	AccessTypeView       AccessType = 0
	AccessTypeDeclassify AccessType = 1
	AccessTypeAttach     AccessType = 2
)

// Flow represents a transaction record with encrypted role-based payloads.
type Flow struct {
	FlowID          util.Uint256 // Unique identifier (hash of contents)
	Bucket          string       // Time bucket (e.g., "2025-01-15T14:00:00Z")
	Tag             string       // Category: COFFEE, DONATION, SUPPLY, etc.
	Amount          uint64       // Value in smallest unit
	Timestamp       uint32       // Block height when recorded
	Creator         util.Uint160 // Who created the flow

	// Encrypted payloads - each role has its own encrypted view
	ConsumerData    []byte // Encrypted with Consumer key
	MerchantData    []byte // Encrypted with Merchant key
	DistributorData []byte // Encrypted with Distributor key
	ProducerData    []byte // Encrypted with Producer key
	NGOData         []byte // Encrypted with NGO key
	AuditorData     []byte // Encrypted with Auditor key (requires declassify)

	// Participants (script hashes)
	Participants []util.Uint160

	// Chain of custody
	PreviousFlowID util.Uint256 // Links flows in a supply chain

	Status FlowStatus
}

// ToStackItem converts Flow to a stack item.
func (f *Flow) ToStackItem() stackitem.Item {
	participants := make([]stackitem.Item, len(f.Participants))
	for i, p := range f.Participants {
		participants[i] = stackitem.NewByteArray(p.BytesBE())
	}

	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewByteArray(f.FlowID.BytesBE()),
		stackitem.NewByteArray([]byte(f.Bucket)),
		stackitem.NewByteArray([]byte(f.Tag)),
		stackitem.NewBigInteger(big.NewInt(int64(f.Amount))),
		stackitem.NewBigInteger(big.NewInt(int64(f.Timestamp))),
		stackitem.NewByteArray(f.Creator.BytesBE()),
		stackitem.NewByteArray(f.ConsumerData),
		stackitem.NewByteArray(f.MerchantData),
		stackitem.NewByteArray(f.DistributorData),
		stackitem.NewByteArray(f.ProducerData),
		stackitem.NewByteArray(f.NGOData),
		stackitem.NewByteArray(f.AuditorData),
		stackitem.NewArray(participants),
		stackitem.NewByteArray(f.PreviousFlowID.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(f.Status))),
	})
}

// FromStackItem populates Flow from a stack item.
func (f *Flow) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 15 {
		return errPalamInvalidStackItem
	}

	flowIDBytes, err := arr[0].TryBytes()
	if err != nil {
		return err
	}
	f.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes)
	if err != nil {
		return err
	}

	bucketBytes, err := arr[1].TryBytes()
	if err != nil {
		return err
	}
	f.Bucket = string(bucketBytes)

	tagBytes, err := arr[2].TryBytes()
	if err != nil {
		return err
	}
	f.Tag = string(tagBytes)

	amount, err := arr[3].TryInteger()
	if err != nil {
		return err
	}
	f.Amount = amount.Uint64()

	timestamp, err := arr[4].TryInteger()
	if err != nil {
		return err
	}
	f.Timestamp = uint32(timestamp.Uint64())

	creatorBytes, err := arr[5].TryBytes()
	if err != nil {
		return err
	}
	f.Creator, err = util.Uint160DecodeBytesBE(creatorBytes)
	if err != nil {
		return err
	}

	f.ConsumerData, err = arr[6].TryBytes()
	if err != nil {
		return err
	}
	f.MerchantData, err = arr[7].TryBytes()
	if err != nil {
		return err
	}
	f.DistributorData, err = arr[8].TryBytes()
	if err != nil {
		return err
	}
	f.ProducerData, err = arr[9].TryBytes()
	if err != nil {
		return err
	}
	f.NGOData, err = arr[10].TryBytes()
	if err != nil {
		return err
	}
	f.AuditorData, err = arr[11].TryBytes()
	if err != nil {
		return err
	}

	participantsArr, ok := arr[12].Value().([]stackitem.Item)
	if !ok {
		return errPalamInvalidStackItem
	}
	f.Participants = make([]util.Uint160, len(participantsArr))
	for i, p := range participantsArr {
		pBytes, err := p.TryBytes()
		if err != nil {
			return err
		}
		f.Participants[i], err = util.Uint160DecodeBytesBE(pBytes)
		if err != nil {
			return err
		}
	}

	prevFlowBytes, err := arr[13].TryBytes()
	if err != nil {
		return err
	}
	if len(prevFlowBytes) > 0 {
		f.PreviousFlowID, err = util.Uint256DecodeBytesBE(prevFlowBytes)
		if err != nil {
			return err
		}
	}

	status, err := arr[14].TryInteger()
	if err != nil {
		return err
	}
	f.Status = FlowStatus(status.Uint64())

	return nil
}

// DeclassifyRequest represents a request for elevated access to flow data.
type DeclassifyRequest struct {
	RequestID     uint64       // Unique identifier
	FlowID        util.Uint256 // Target flow
	CaseID        string       // Legal case reference
	Reason        string       // Justification for access
	Requester     util.Uint160 // Script hash of requester
	RequesterRole PalamRole   // Must be Auditor

	// Approval tracking
	RequiredApprovals uint32         // e.g., 2 of 3
	Approvals         []util.Uint160 // Script hashes of approvers
	ApprovalTimes     []uint32       // Block heights of approvals

	// Status
	Status    DeclassifyStatus
	CreatedAt uint32 // Block height when created
	ExpiresAt uint32 // Request expires if not approved
	GrantedAt uint32 // When access was granted
}

// ToStackItem converts DeclassifyRequest to a stack item.
func (d *DeclassifyRequest) ToStackItem() stackitem.Item {
	approvals := make([]stackitem.Item, len(d.Approvals))
	for i, a := range d.Approvals {
		approvals[i] = stackitem.NewByteArray(a.BytesBE())
	}

	approvalTimes := make([]stackitem.Item, len(d.ApprovalTimes))
	for i, t := range d.ApprovalTimes {
		approvalTimes[i] = stackitem.NewBigInteger(big.NewInt(int64(t)))
	}

	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(d.RequestID))),
		stackitem.NewByteArray(d.FlowID.BytesBE()),
		stackitem.NewByteArray([]byte(d.CaseID)),
		stackitem.NewByteArray([]byte(d.Reason)),
		stackitem.NewByteArray(d.Requester.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(d.RequesterRole))),
		stackitem.NewBigInteger(big.NewInt(int64(d.RequiredApprovals))),
		stackitem.NewArray(approvals),
		stackitem.NewArray(approvalTimes),
		stackitem.NewBigInteger(big.NewInt(int64(d.Status))),
		stackitem.NewBigInteger(big.NewInt(int64(d.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(d.ExpiresAt))),
		stackitem.NewBigInteger(big.NewInt(int64(d.GrantedAt))),
	})
}

// FromStackItem populates DeclassifyRequest from a stack item.
func (d *DeclassifyRequest) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 13 {
		return errPalamInvalidStackItem
	}

	requestID, err := arr[0].TryInteger()
	if err != nil {
		return err
	}
	d.RequestID = requestID.Uint64()

	flowIDBytes, err := arr[1].TryBytes()
	if err != nil {
		return err
	}
	d.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes)
	if err != nil {
		return err
	}

	caseIDBytes, err := arr[2].TryBytes()
	if err != nil {
		return err
	}
	d.CaseID = string(caseIDBytes)

	reasonBytes, err := arr[3].TryBytes()
	if err != nil {
		return err
	}
	d.Reason = string(reasonBytes)

	requesterBytes, err := arr[4].TryBytes()
	if err != nil {
		return err
	}
	d.Requester, err = util.Uint160DecodeBytesBE(requesterBytes)
	if err != nil {
		return err
	}

	role, err := arr[5].TryInteger()
	if err != nil {
		return err
	}
	d.RequesterRole = PalamRole(role.Uint64())

	required, err := arr[6].TryInteger()
	if err != nil {
		return err
	}
	d.RequiredApprovals = uint32(required.Uint64())

	approvalsArr, ok := arr[7].Value().([]stackitem.Item)
	if !ok {
		return errPalamInvalidStackItem
	}
	d.Approvals = make([]util.Uint160, len(approvalsArr))
	for i, a := range approvalsArr {
		aBytes, err := a.TryBytes()
		if err != nil {
			return err
		}
		d.Approvals[i], err = util.Uint160DecodeBytesBE(aBytes)
		if err != nil {
			return err
		}
	}

	timesArr, ok := arr[8].Value().([]stackitem.Item)
	if !ok {
		return errPalamInvalidStackItem
	}
	d.ApprovalTimes = make([]uint32, len(timesArr))
	for i, t := range timesArr {
		tInt, err := t.TryInteger()
		if err != nil {
			return err
		}
		d.ApprovalTimes[i] = uint32(tInt.Uint64())
	}

	status, err := arr[9].TryInteger()
	if err != nil {
		return err
	}
	d.Status = DeclassifyStatus(status.Uint64())

	createdAt, err := arr[10].TryInteger()
	if err != nil {
		return err
	}
	d.CreatedAt = uint32(createdAt.Uint64())

	expiresAt, err := arr[11].TryInteger()
	if err != nil {
		return err
	}
	d.ExpiresAt = uint32(expiresAt.Uint64())

	grantedAt, err := arr[12].TryInteger()
	if err != nil {
		return err
	}
	d.GrantedAt = uint32(grantedAt.Uint64())

	return nil
}

// AccessLog represents a record of data access.
type AccessLog struct {
	LogID      uint64       // Unique identifier
	FlowID     util.Uint256 // Which flow was accessed
	Accessor   util.Uint160 // Who accessed
	AccessType AccessType   // Type of access
	Timestamp  uint32       // Block height
	Details    string       // Additional context
}

// ToStackItem converts AccessLog to a stack item.
func (a *AccessLog) ToStackItem() stackitem.Item {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(a.LogID))),
		stackitem.NewByteArray(a.FlowID.BytesBE()),
		stackitem.NewByteArray(a.Accessor.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(a.AccessType))),
		stackitem.NewBigInteger(big.NewInt(int64(a.Timestamp))),
		stackitem.NewByteArray([]byte(a.Details)),
	})
}

// FromStackItem populates AccessLog from a stack item.
func (a *AccessLog) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 6 {
		return errPalamInvalidStackItem
	}

	logID, err := arr[0].TryInteger()
	if err != nil {
		return err
	}
	a.LogID = logID.Uint64()

	flowIDBytes, err := arr[1].TryBytes()
	if err != nil {
		return err
	}
	a.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes)
	if err != nil {
		return err
	}

	accessorBytes, err := arr[2].TryBytes()
	if err != nil {
		return err
	}
	a.Accessor, err = util.Uint160DecodeBytesBE(accessorBytes)
	if err != nil {
		return err
	}

	accessType, err := arr[3].TryInteger()
	if err != nil {
		return err
	}
	a.AccessType = AccessType(accessType.Uint64())

	timestamp, err := arr[4].TryInteger()
	if err != nil {
		return err
	}
	a.Timestamp = uint32(timestamp.Uint64())

	detailsBytes, err := arr[5].TryBytes()
	if err != nil {
		return err
	}
	a.Details = string(detailsBytes)

	return nil
}

// FlowAttachment represents additional data attached to a flow.
type FlowAttachment struct {
	AttachmentID   uint64       // Unique identifier
	FlowID         util.Uint256 // Parent flow
	AttachmentType string       // Type of attachment
	EncryptedData  []byte       // Encrypted content
	Attacher       util.Uint160 // Who attached
	AttachedAt     uint32       // Block height
}

// ToStackItem converts FlowAttachment to a stack item.
func (fa *FlowAttachment) ToStackItem() stackitem.Item {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(fa.AttachmentID))),
		stackitem.NewByteArray(fa.FlowID.BytesBE()),
		stackitem.NewByteArray([]byte(fa.AttachmentType)),
		stackitem.NewByteArray(fa.EncryptedData),
		stackitem.NewByteArray(fa.Attacher.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(fa.AttachedAt))),
	})
}

// FromStackItem populates FlowAttachment from a stack item.
func (fa *FlowAttachment) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 6 {
		return errPalamInvalidStackItem
	}

	attachmentID, err := arr[0].TryInteger()
	if err != nil {
		return err
	}
	fa.AttachmentID = attachmentID.Uint64()

	flowIDBytes, err := arr[1].TryBytes()
	if err != nil {
		return err
	}
	fa.FlowID, err = util.Uint256DecodeBytesBE(flowIDBytes)
	if err != nil {
		return err
	}

	typeBytes, err := arr[2].TryBytes()
	if err != nil {
		return err
	}
	fa.AttachmentType = string(typeBytes)

	fa.EncryptedData, err = arr[3].TryBytes()
	if err != nil {
		return err
	}

	attacherBytes, err := arr[4].TryBytes()
	if err != nil {
		return err
	}
	fa.Attacher, err = util.Uint160DecodeBytesBE(attacherBytes)
	if err != nil {
		return err
	}

	attachedAt, err := arr[5].TryInteger()
	if err != nil {
		return err
	}
	fa.AttachedAt = uint32(attachedAt.Uint64())

	return nil
}

// RolePermissions defines what each role can do.
type RolePermissions struct {
	CanViewAggregate     bool // See totals without identities
	CanViewIdentities    bool // See participant identities
	CanViewDocuments     bool // See attached documents
	CanAttachData        bool // Add metadata to flows
	CanRequestDeclassify bool // Initiate declassification
	CanApproveDeclassify bool // Vote on declassification requests
}

// ToStackItem converts RolePermissions to a stack item.
func (rp *RolePermissions) ToStackItem() stackitem.Item {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBool(rp.CanViewAggregate),
		stackitem.NewBool(rp.CanViewIdentities),
		stackitem.NewBool(rp.CanViewDocuments),
		stackitem.NewBool(rp.CanAttachData),
		stackitem.NewBool(rp.CanRequestDeclassify),
		stackitem.NewBool(rp.CanApproveDeclassify),
	})
}

// FromStackItem populates RolePermissions from a stack item.
func (rp *RolePermissions) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 6 {
		return errPalamInvalidStackItem
	}

	viewAgg, err := arr[0].TryBool()
	if err != nil {
		return err
	}
	rp.CanViewAggregate = viewAgg

	viewId, err := arr[1].TryBool()
	if err != nil {
		return err
	}
	rp.CanViewIdentities = viewId

	viewDoc, err := arr[2].TryBool()
	if err != nil {
		return err
	}
	rp.CanViewDocuments = viewDoc

	attach, err := arr[3].TryBool()
	if err != nil {
		return err
	}
	rp.CanAttachData = attach

	reqDecl, err := arr[4].TryBool()
	if err != nil {
		return err
	}
	rp.CanRequestDeclassify = reqDecl

	appDecl, err := arr[5].TryBool()
	if err != nil {
		return err
	}
	rp.CanApproveDeclassify = appDecl

	return nil
}

// PalamConfig represents configurable parameters for TransparencyLedger.
type PalamConfig struct {
	MinApprovals      uint32 // Minimum approvals for declassification (default: 2)
	MaxApprovals      uint32 // Maximum approvers allowed (default: 5)
	DefaultExpiration uint32 // Default expiration in blocks (default: 7 days worth)
	LogAllAccess      bool   // Whether to log all access (default: true)
	LogRetentionDays  uint32 // How long to keep logs (default: 365)
	MaxFlowsPerBlock  uint32 // Rate limit for flows (default: 1000)
	MaxRequestsPerDay uint32 // Rate limit for declassify requests (default: 10)
}

// ToStackItem converts PalamConfig to a stack item.
func (lc *PalamConfig) ToStackItem() stackitem.Item {
	return stackitem.NewArray([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(lc.MinApprovals))),
		stackitem.NewBigInteger(big.NewInt(int64(lc.MaxApprovals))),
		stackitem.NewBigInteger(big.NewInt(int64(lc.DefaultExpiration))),
		stackitem.NewBool(lc.LogAllAccess),
		stackitem.NewBigInteger(big.NewInt(int64(lc.LogRetentionDays))),
		stackitem.NewBigInteger(big.NewInt(int64(lc.MaxFlowsPerBlock))),
		stackitem.NewBigInteger(big.NewInt(int64(lc.MaxRequestsPerDay))),
	})
}

// FromStackItem populates PalamConfig from a stack item.
func (lc *PalamConfig) FromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok || len(arr) < 7 {
		return errPalamInvalidStackItem
	}

	minApprovals, err := arr[0].TryInteger()
	if err != nil {
		return err
	}
	lc.MinApprovals = uint32(minApprovals.Uint64())

	maxApprovals, err := arr[1].TryInteger()
	if err != nil {
		return err
	}
	lc.MaxApprovals = uint32(maxApprovals.Uint64())

	defaultExp, err := arr[2].TryInteger()
	if err != nil {
		return err
	}
	lc.DefaultExpiration = uint32(defaultExp.Uint64())

	logAll, err := arr[3].TryBool()
	if err != nil {
		return err
	}
	lc.LogAllAccess = logAll

	retention, err := arr[4].TryInteger()
	if err != nil {
		return err
	}
	lc.LogRetentionDays = uint32(retention.Uint64())

	maxFlows, err := arr[5].TryInteger()
	if err != nil {
		return err
	}
	lc.MaxFlowsPerBlock = uint32(maxFlows.Uint64())

	maxReqs, err := arr[6].TryInteger()
	if err != nil {
		return err
	}
	lc.MaxRequestsPerDay = uint32(maxReqs.Uint64())

	return nil
}

// DefaultPalamConfig returns the default configuration.
func DefaultPalamConfig() *PalamConfig {
	return &PalamConfig{
		MinApprovals:      2,
		MaxApprovals:      5,
		DefaultExpiration: 604800, // ~7 days in blocks (1 block/sec)
		LogAllAccess:      true,
		LogRetentionDays:  365,
		MaxFlowsPerBlock:  1000,
		MaxRequestsPerDay: 10,
	}
}

// DefaultRolePermissions returns permissions for each role.
func DefaultRolePermissions(role PalamRole) *RolePermissions {
	switch role {
	case PalamRoleConsumer:
		return &RolePermissions{
			CanViewAggregate:     true,
			CanViewIdentities:    false,
			CanViewDocuments:     false,
			CanAttachData:        false,
			CanRequestDeclassify: false,
			CanApproveDeclassify: false,
		}
	case PalamRoleMerchant, PalamRoleDistributor, PalamRoleProducer, PalamRoleNGO:
		return &RolePermissions{
			CanViewAggregate:     true,
			CanViewIdentities:    true,
			CanViewDocuments:     true,
			CanAttachData:        true,
			CanRequestDeclassify: false,
			CanApproveDeclassify: false,
		}
	case PalamRoleAuditor:
		return &RolePermissions{
			CanViewAggregate:     true,
			CanViewIdentities:    false,
			CanViewDocuments:     false,
			CanAttachData:        false,
			CanRequestDeclassify: true,
			CanApproveDeclassify: false,
		}
	default:
		return &RolePermissions{}
	}
}
