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"
)

// HealthcareAccountStatus represents the status of a healthcare account.
type HealthcareAccountStatus uint8

const (
	// HealthcareAccountActive indicates an active account.
	HealthcareAccountActive HealthcareAccountStatus = 0
	// HealthcareAccountSuspended indicates a temporarily suspended account.
	HealthcareAccountSuspended HealthcareAccountStatus = 1
	// HealthcareAccountClosed indicates a permanently closed account.
	HealthcareAccountClosed HealthcareAccountStatus = 2
)

// MedicalRecordType represents the type of medical record.
type MedicalRecordType uint8

const (
	// RecordTypeCheckup indicates a routine checkup.
	RecordTypeCheckup MedicalRecordType = 0
	// RecordTypeTreatment indicates a treatment.
	RecordTypeTreatment MedicalRecordType = 1
	// RecordTypeEmergency indicates an emergency visit.
	RecordTypeEmergency MedicalRecordType = 2
	// RecordTypePrescription indicates a prescription.
	RecordTypePrescription MedicalRecordType = 3
	// RecordTypeLabResult indicates lab results.
	RecordTypeLabResult MedicalRecordType = 4
	// RecordTypeVaccination indicates a vaccination.
	RecordTypeVaccination MedicalRecordType = 5
	// RecordTypeMentalHealth indicates mental health services.
	RecordTypeMentalHealth MedicalRecordType = 6
	// RecordTypePreventive indicates preventive care.
	RecordTypePreventive MedicalRecordType = 7
)

// AccessLevel represents the level of access granted to a provider.
type AccessLevel uint8

const (
	// AccessLevelNone indicates no access.
	AccessLevelNone AccessLevel = 0
	// AccessLevelEmergency indicates emergency-only access.
	AccessLevelEmergency AccessLevel = 1
	// AccessLevelLimited indicates limited access (specific record types).
	AccessLevelLimited AccessLevel = 2
	// AccessLevelFull indicates full access to all records.
	AccessLevelFull AccessLevel = 3
)

// ProviderStatus represents the status of a healthcare provider.
type ProviderStatus uint8

const (
	// ProviderStatusActive indicates an active provider.
	ProviderStatusActive ProviderStatus = 0
	// ProviderStatusSuspended indicates a suspended provider.
	ProviderStatusSuspended ProviderStatus = 1
	// ProviderStatusRevoked indicates a revoked provider.
	ProviderStatusRevoked ProviderStatus = 2
)

// HealthcareAccount represents a citizen's healthcare account.
type HealthcareAccount struct {
	VitaID           uint64                  // Owner's Vita token ID
	Owner            util.Uint160            // Owner's address
	AnnualAllocation uint64                  // Annual healthcare credits
	CreditsUsed      uint64                  // Credits used this year
	CreditsAvailable uint64                  // Available healthcare credits
	BiologicalAge    uint32                  // Biological age (Salus-adjusted)
	LastCheckup      uint32                  // Block height of last checkup
	Status           HealthcareAccountStatus // Account status
	CreatedAt        uint32                  // Block height when created
	UpdatedAt        uint32                  // Block height of last update
}

// ToStackItem implements stackitem.Convertible interface.
func (a *HealthcareAccount) 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.AnnualAllocation))),
		stackitem.NewBigInteger(big.NewInt(int64(a.CreditsUsed))),
		stackitem.NewBigInteger(big.NewInt(int64(a.CreditsAvailable))),
		stackitem.NewBigInteger(big.NewInt(int64(a.BiologicalAge))),
		stackitem.NewBigInteger(big.NewInt(int64(a.LastCheckup))),
		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 *HealthcareAccount) 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))
	}

	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)
	}

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

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

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

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

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

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

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

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

	return nil
}

// MedicalRecord represents a medical record reference (data stored off-chain).
type MedicalRecord struct {
	ID          uint64            // Unique record ID
	VitaID      uint64            // Patient's Vita ID
	Patient     util.Uint160      // Patient's address
	Provider    util.Uint160      // Healthcare provider's address
	RecordType  MedicalRecordType // Type of medical record
	ContentHash util.Uint256      // Hash of encrypted off-chain data
	CreditsUsed uint64            // Healthcare credits used
	CreatedAt   uint32            // Block height when created
	IsActive    bool              // Whether record is valid
}

// ToStackItem implements stackitem.Convertible interface.
func (r *MedicalRecord) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(r.ID))),
		stackitem.NewBigInteger(big.NewInt(int64(r.VitaID))),
		stackitem.NewByteArray(r.Patient.BytesBE()),
		stackitem.NewByteArray(r.Provider.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(r.RecordType))),
		stackitem.NewByteArray(r.ContentHash.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(r.CreditsUsed))),
		stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))),
		stackitem.NewBool(r.IsActive),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (r *MedicalRecord) 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)
	}
	r.ID = id.Uint64()

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

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

	provider, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid provider: %w", err)
	}
	r.Provider, err = util.Uint160DecodeBytesBE(provider)
	if err != nil {
		return fmt.Errorf("invalid provider address: %w", err)
	}

	recordType, err := items[4].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid recordType: %w", err)
	}
	r.RecordType = MedicalRecordType(recordType.Uint64())

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

	creditsUsed, err := items[6].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid creditsUsed: %w", err)
	}
	r.CreditsUsed = creditsUsed.Uint64()

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

	isActive, err := items[8].TryBool()
	if err != nil {
		return fmt.Errorf("invalid isActive: %w", err)
	}
	r.IsActive = isActive

	return nil
}

// ProviderAuthorization represents a healthcare provider's access authorization.
type ProviderAuthorization struct {
	ID          uint64       // Authorization ID
	VitaID      uint64       // Patient's Vita ID
	Patient     util.Uint160 // Patient's address
	Provider    util.Uint160 // Healthcare provider's address
	AccessLevel AccessLevel  // Level of access granted
	StartsAt    uint32       // Block height when access starts
	ExpiresAt   uint32       // Block height when access expires (0 = no expiry)
	IsActive    bool         // Whether authorization is currently active
	GrantedAt   uint32       // Block height when granted
}

// ToStackItem implements stackitem.Convertible interface.
func (p *ProviderAuthorization) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(p.ID))),
		stackitem.NewBigInteger(big.NewInt(int64(p.VitaID))),
		stackitem.NewByteArray(p.Patient.BytesBE()),
		stackitem.NewByteArray(p.Provider.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(p.AccessLevel))),
		stackitem.NewBigInteger(big.NewInt(int64(p.StartsAt))),
		stackitem.NewBigInteger(big.NewInt(int64(p.ExpiresAt))),
		stackitem.NewBool(p.IsActive),
		stackitem.NewBigInteger(big.NewInt(int64(p.GrantedAt))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (p *ProviderAuthorization) 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)
	}
	p.ID = id.Uint64()

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

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

	provider, err := items[3].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid provider: %w", err)
	}
	p.Provider, err = util.Uint160DecodeBytesBE(provider)
	if err != nil {
		return fmt.Errorf("invalid provider address: %w", err)
	}

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

	startsAt, err := items[5].TryInteger()
	if err != nil {
		return fmt.Errorf("invalid startsAt: %w", err)
	}
	p.StartsAt = uint32(startsAt.Uint64())

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

	isActive, err := items[7].TryBool()
	if err != nil {
		return fmt.Errorf("invalid isActive: %w", err)
	}
	p.IsActive = isActive

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

	return nil
}

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

// IsValid checks if the authorization is currently valid.
func (p *ProviderAuthorization) IsValid(currentBlock uint32) bool {
	return p.IsActive && currentBlock >= p.StartsAt && !p.IsExpired(currentBlock)
}

// HealthcareProvider represents a registered healthcare provider.
type HealthcareProvider struct {
	Address     util.Uint160   // Provider's address
	Name        string         // Provider name
	ProviderID  uint64         // Unique provider ID
	Specialty   string         // Medical specialty
	LicenseHash util.Uint256   // Hash of license documentation
	Status      ProviderStatus // Provider status
	RegisteredAt uint32        // Block height when registered
	UpdatedAt   uint32         // Block height of last update
}

// ToStackItem implements stackitem.Convertible interface.
func (p *HealthcareProvider) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewByteArray(p.Address.BytesBE()),
		stackitem.NewByteArray([]byte(p.Name)),
		stackitem.NewBigInteger(big.NewInt(int64(p.ProviderID))),
		stackitem.NewByteArray([]byte(p.Specialty)),
		stackitem.NewByteArray(p.LicenseHash.BytesBE()),
		stackitem.NewBigInteger(big.NewInt(int64(p.Status))),
		stackitem.NewBigInteger(big.NewInt(int64(p.RegisteredAt))),
		stackitem.NewBigInteger(big.NewInt(int64(p.UpdatedAt))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (p *HealthcareProvider) 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))
	}

	address, err := items[0].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid address: %w", err)
	}
	p.Address, err = util.Uint160DecodeBytesBE(address)
	if err != nil {
		return fmt.Errorf("invalid provider address: %w", err)
	}

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

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

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

	licenseHash, err := items[4].TryBytes()
	if err != nil {
		return fmt.Errorf("invalid licenseHash: %w", err)
	}
	p.LicenseHash, err = util.Uint256DecodeBytesBE(licenseHash)
	if err != nil {
		return fmt.Errorf("invalid licenseHash value: %w", err)
	}

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

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

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

	return nil
}

// EmergencyAccess represents an emergency access grant.
type EmergencyAccess struct {
	ID          uint64       // Emergency access ID
	VitaID      uint64       // Patient's Vita ID
	Patient     util.Uint160 // Patient's address
	Provider    util.Uint160 // Provider who accessed
	Reason      string       // Emergency reason
	GrantedAt   uint32       // Block height when granted
	ExpiresAt   uint32       // Block height when expires
	WasReviewed bool         // Whether access was reviewed
}

// ToStackItem implements stackitem.Convertible interface.
func (e *EmergencyAccess) 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.Patient.BytesBE()),
		stackitem.NewByteArray(e.Provider.BytesBE()),
		stackitem.NewByteArray([]byte(e.Reason)),
		stackitem.NewBigInteger(big.NewInt(int64(e.GrantedAt))),
		stackitem.NewBigInteger(big.NewInt(int64(e.ExpiresAt))),
		stackitem.NewBool(e.WasReviewed),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (e *EmergencyAccess) 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))
	}

	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()

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

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

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

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

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

	wasReviewed, err := items[7].TryBool()
	if err != nil {
		return fmt.Errorf("invalid wasReviewed: %w", err)
	}
	e.WasReviewed = wasReviewed

	return nil
}

// SalusConfig represents configurable parameters for the Salus contract.
type SalusConfig struct {
	DefaultAnnualCredits   uint64 // Default annual healthcare credits
	EmergencyAccessDuration uint32 // Blocks for emergency access (default ~24 hours)
	PreventiveCareBonus    uint64 // Bonus credits for preventive care
	MaxAuthorizationDuration uint32 // Maximum authorization duration in blocks
}

// ToStackItem implements stackitem.Convertible interface.
func (c *SalusConfig) ToStackItem() (stackitem.Item, error) {
	return stackitem.NewStruct([]stackitem.Item{
		stackitem.NewBigInteger(big.NewInt(int64(c.DefaultAnnualCredits))),
		stackitem.NewBigInteger(big.NewInt(int64(c.EmergencyAccessDuration))),
		stackitem.NewBigInteger(big.NewInt(int64(c.PreventiveCareBonus))),
		stackitem.NewBigInteger(big.NewInt(int64(c.MaxAuthorizationDuration))),
	}), nil
}

// FromStackItem implements stackitem.Convertible interface.
func (c *SalusConfig) 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))
	}

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

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

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

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

	return nil
}
