package state

import (
	"errors"
	"fmt"
	"math/big"

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

// EducationAccountStatus represents the status of an education account.
type EducationAccountStatus uint8

const (
	// EducationAccountActive indicates an active account.
	EducationAccountActive EducationAccountStatus = 0
	// EducationAccountSuspended indicates a temporarily suspended account.
	EducationAccountSuspended EducationAccountStatus = 1
	// EducationAccountClosed indicates a permanently closed account.
	EducationAccountClosed EducationAccountStatus = 2
)

// CertificationStatus represents the validity status of a certification.
type CertificationStatus uint8

const (
	// CertificationActive indicates a valid active certification.
	CertificationActive CertificationStatus = 0
	// CertificationExpired indicates an expired certification.
	CertificationExpired CertificationStatus = 1
	// CertificationRevoked indicates a revoked certification.
	CertificationRevoked CertificationStatus = 2
)

// EnrollmentStatus represents the status of a program enrollment.
type EnrollmentStatus uint8

const (
	// EnrollmentActive indicates an active enrollment.
	EnrollmentActive EnrollmentStatus = 0
	// EnrollmentCompleted indicates successful completion.
	EnrollmentCompleted EnrollmentStatus = 1
	// EnrollmentWithdrawn indicates voluntary withdrawal.
	EnrollmentWithdrawn EnrollmentStatus = 2
	// EnrollmentTransferred indicates transfer to another institution.
	EnrollmentTransferred EnrollmentStatus = 3
)

// EducationAccount represents a citizen's lifelong learning account.
type EducationAccount struct {
	VitaID           uint64                 // Owner's Vita token ID
	Owner            util.Uint160           // Owner's address
	TotalCredits     uint64                 // Lifetime credits received
	UsedCredits      uint64                 // Credits spent on education
	AvailableCredits uint64                 // Current balance
	Status           EducationAccountStatus // Account status
	CreatedAt        uint32                 // Block height when created
	UpdatedAt        uint32                 // Block height of last modification
}

// ToStackItem implements stackitem.Convertible interface.
func (a *EducationAccount) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(a.VitaID))),
		stackitem.NewByteArray(a.Owner.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(a.TotalCredits))),
		stackitem.NewBigInteger(big.NewInt(int64(a.UsedCredits))),
		stackitem.NewBigInteger(big.NewInt(int64(a.AvailableCredits))),
		stackitem.NewBigInteger(big.NewInt(int64(a.Status))),
		stackitem.NewBigInteger(big.NewInt(int64(a.CreatedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(a.UpdatedAt))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (a *EducationAccount) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 8 {
		return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items))
	}

	vitaID, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid vitaID: %w", err)
	}
	a.VitaID = vitaID.Uint64()

	owner, err := items[1].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid owner: %w", err)
	}
	a.Owner, err = util.Uint160DecodeBytesBE(owner)
	if err != nil {
		return fmt.Errorf("invalid owner address: %w", err)
	}

	totalCredits, err := items[2].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid totalCredits: %w", err)
	}
	a.TotalCredits = totalCredits.Uint64()

	usedCredits, err := items[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid usedCredits: %w", err)
	}
	a.UsedCredits = usedCredits.Uint64()

	availableCredits, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid availableCredits: %w", err)
	}
	a.AvailableCredits = availableCredits.Uint64()

	status, err := items[5].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid status: %w", err)
	}
	a.Status = EducationAccountStatus(status.Uint64())

	createdAt, err := items[6].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid createdAt: %w", err)
	}
	a.CreatedAt = uint32(createdAt.Uint64())

	updatedAt, err := items[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid updatedAt: %w", err)
	}
	a.UpdatedAt = uint32(updatedAt.Uint64())

	return nil
}

// Certification represents a verified skill or credential.
type Certification struct {
	ID          uint64              // Unique certification ID
	VitaID      uint64              // Owner's Vita ID
	Owner       util.Uint160        // Owner's address
	CertType    string              // Type of certification
	Name        string              // Certification name
	Institution util.Uint160        // Issuing institution
	ContentHash util.Uint256        // Off-chain content hash
	IssuedAt    uint32              // Block height when issued
	ExpiresAt   uint32              // 0 = never expires
	Status      CertificationStatus // Certification status
}

// ToStackItem implements stackitem.Convertible interface.
func (c *Certification) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(c.ID))),
		stackitem.NewBigInteger(big.NewInt(int64(c.VitaID))),
		stackitem.NewByteArray(c.Owner.BytesBE()),
		stackitem.NewByteArray([]byte(c.CertType)),
		stackitem.NewByteArray([]byte(c.Name)),
		stackitem.NewByteArray(c.Institution.BytesBE()),
		stackitem.NewByteArray(c.ContentHash.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(c.IssuedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(c.ExpiresAt))),
		stackitem.NewBigInteger(big.NewInt(int64(c.Status))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (c *Certification) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 10 {
		return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
	}

	id, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid id: %w", err)
	}
	c.ID = id.Uint64()

	vitaID, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid vitaID: %w", err)
	}
	c.VitaID = vitaID.Uint64()

	owner, err := items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid owner: %w", err)
	}
	c.Owner, err = util.Uint160DecodeBytesBE(owner)
	if err != nil {
		return fmt.Errorf("invalid owner address: %w", err)
	}

	certType, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid certType: %w", err)
	}
	c.CertType = string(certType)

	name, err := items[4].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid name: %w", err)
	}
	c.Name = string(name)

	institution, err := items[5].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid institution: %w", err)
	}
	c.Institution, err = util.Uint160DecodeBytesBE(institution)
	if err != nil {
		return fmt.Errorf("invalid institution address: %w", err)
	}

	contentHash, err := items[6].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid contentHash: %w", err)
	}
	c.ContentHash, err = util.Uint256DecodeBytesBE(contentHash)
	if err != nil {
		return fmt.Errorf("invalid contentHash value: %w", err)
	}

	issuedAt, err := items[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid issuedAt: %w", err)
	}
	c.IssuedAt = uint32(issuedAt.Uint64())

	expiresAt, err := items[8].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid expiresAt: %w", err)
	}
	c.ExpiresAt = uint32(expiresAt.Uint64())

	status, err := items[9].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid status: %w", err)
	}
	c.Status = CertificationStatus(status.Uint64())

	return nil
}

// IsExpired checks if the certification has expired.
func (c *Certification) IsExpired(currentBlock uint32) bool {
	return c.ExpiresAt != 0 && c.ExpiresAt <= currentBlock
}

// IsValid checks if the certification is currently valid.
func (c *Certification) IsValid(currentBlock uint32) bool {
	return c.Status == CertificationActive && !c.IsExpired(currentBlock)
}

// Enrollment represents a program enrollment record.
type Enrollment struct {
	ID               uint64           // Unique enrollment ID
	VitaID           uint64           // Student's Vita ID
	Student          util.Uint160     // Student's address
	ProgramID        string           // Program identifier
	Institution      util.Uint160     // Educational institution
	CreditsAllocated uint64           // Credits committed to program
	StartedAt        uint32           // Block height when started
	CompletedAt      uint32           // Block height when completed (0 = ongoing)
	Status           EnrollmentStatus // Enrollment status
}

// ToStackItem implements stackitem.Convertible interface.
func (e *Enrollment) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(e.ID))),
		stackitem.NewBigInteger(big.NewInt(int64(e.VitaID))),
		stackitem.NewByteArray(e.Student.BytesBE()),
		stackitem.NewByteArray([]byte(e.ProgramID)),
		stackitem.NewByteArray(e.Institution.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(e.CreditsAllocated))),
		stackitem.NewBigInteger(big.NewInt(int64(e.StartedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(e.CompletedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(e.Status))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (e *Enrollment) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 9 {
		return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items))
	}

	id, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid id: %w", err)
	}
	e.ID = id.Uint64()

	vitaID, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid vitaID: %w", err)
	}
	e.VitaID = vitaID.Uint64()

	student, err := items[2].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid student: %w", err)
	}
	e.Student, err = util.Uint160DecodeBytesBE(student)
	if err != nil {
		return fmt.Errorf("invalid student address: %w", err)
	}

	programID, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid programID: %w", err)
	}
	e.ProgramID = string(programID)

	institution, err := items[4].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid institution: %w", err)
	}
	e.Institution, err = util.Uint160DecodeBytesBE(institution)
	if err != nil {
		return fmt.Errorf("invalid institution address: %w", err)
	}

	creditsAllocated, err := items[5].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid creditsAllocated: %w", err)
	}
	e.CreditsAllocated = creditsAllocated.Uint64()

	startedAt, err := items[6].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid startedAt: %w", err)
	}
	e.StartedAt = uint32(startedAt.Uint64())

	completedAt, err := items[7].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid completedAt: %w", err)
	}
	e.CompletedAt = uint32(completedAt.Uint64())

	status, err := items[8].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid status: %w", err)
	}
	e.Status = EnrollmentStatus(status.Uint64())

	return nil
}

// ScireConfig represents configurable parameters for the Scire contract.
type ScireConfig struct {
	AnnualCreditAllocation uint64 // Default credits per year
	MaxCreditsPerProgram   uint64 // Maximum credits for single program
	CertificationFee       uint64 // VTS fee for issuing certifications (0 = free)
	MinEnrollmentDuration  uint32 // Minimum blocks for enrollment
}

// ToStackItem implements stackitem.Convertible interface.
func (c *ScireConfig) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(c.AnnualCreditAllocation))),
		stackitem.NewBigInteger(big.NewInt(int64(c.MaxCreditsPerProgram))),
		stackitem.NewBigInteger(big.NewInt(int64(c.CertificationFee))),
		stackitem.NewBigInteger(big.NewInt(int64(c.MinEnrollmentDuration))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (c *ScireConfig) FromStackItem(item stackitem.Item) error {
	items, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not a struct")
	}
	if len(items) != 4 {
		return fmt.Errorf("wrong number of elements: expected 4, got %d", len(items))
	}

	annualCredits, err := items[0].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid annualCreditAllocation: %w", err)
	}
	c.AnnualCreditAllocation = annualCredits.Uint64()

	maxCredits, err := items[1].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid maxCreditsPerProgram: %w", err)
	}
	c.MaxCreditsPerProgram = maxCredits.Uint64()

	certFee, err := items[2].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid certificationFee: %w", err)
	}
	c.CertificationFee = certFee.Uint64()

	minDuration, err := items[3].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid minEnrollmentDuration: %w", err)
	}
	c.MinEnrollmentDuration = uint32(minDuration.Uint64())

	return nil
}
