package model

import (
	"strings"

	"github.com/tech/sendico/pkg/db/storable"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model/account_role"
	"github.com/tech/sendico/pkg/mservice"
	"go.mongodb.org/mongo-driver/v2/bson"
)

// AccountType defines the category of account (asset, liability, revenue, expense).
type LedgerAccountType string

const (
	LedgerAccountTypeAsset     LedgerAccountType = "asset"
	LedgerAccountTypeLiability LedgerAccountType = "liability"
	LedgerAccountTypeRevenue   LedgerAccountType = "revenue"
	LedgerAccountTypeExpense   LedgerAccountType = "expense"
)

// AccountStatus tracks the operational state of an account.
type LedgerAccountStatus string

const (
	LedgerAccountStatusActive LedgerAccountStatus = "active"
	LedgerAccountStatusFrozen LedgerAccountStatus = "frozen"
	LedgerAccountStatusClosed LedgerAccountStatus = "closed"
)

type LedgerAccountScope string

const (
	LedgerAccountScopeOrganization LedgerAccountScope = "organization"
	LedgerAccountScopeSystem       LedgerAccountScope = "system"
)

type SystemAccountPurpose string

const (
	SystemAccountPurposeExternalSource SystemAccountPurpose = "external_source"
	SystemAccountPurposeExternalSink   SystemAccountPurpose = "external_sink"
)

// Account represents a ledger account that holds balances for a specific currency.
type LedgerAccount struct {
	storable.Base `bson:",inline" json:",inline"`
	Describable   `bson:",inline" json:",inline"`

	// Scope defines whether the account belongs to an organization
	// or is a system-level ledger account used for internal accounting.
	Scope LedgerAccountScope `bson:"scope" json:"scope"`

	// SystemPurpose specifies the role of a system-scoped account
	// (e.g., external source or sink of funds). Must be set for system accounts
	// and must be nil for organization accounts.
	SystemPurpose *SystemAccountPurpose `bson:"systemPurpose,omitempty" json:"systemPurpose,omitempty"`

	// OrganizationRef links the account to an organization.
	// Must be set for organization accounts and nil for system accounts.
	OrganizationRef *bson.ObjectID `bson:"organizationRef,omitempty" json:"organizationRef,omitempty"`

	// Role defines the functional purpose of the account within an organization
	// (e.g., pending, operating, settlement, hold, etc.).
	// Must be set for organization accounts and omitted for system accounts.
	Role account_role.AccountRole `bson:"role,omitempty" json:"role,omitempty"`

	// AccountCode is a logical classification code of the account
	// (e.g., "asset:cash:usd") used for reporting and grouping.
	AccountCode string `bson:"accountCode" json:"accountCode"`

	// Currency is the ISO 4217 currency code the account operates in.
	Currency string `bson:"currency" json:"currency"`

	// AccountType defines the accounting category of the account
	// (asset, liability, revenue, expense).
	AccountType LedgerAccountType `bson:"accountType" json:"accountType"`

	// Status represents the operational state of the account.
	Status LedgerAccountStatus `bson:"status" json:"status"`

	// AllowNegative defines whether the account is allowed to have
	// a negative balance (used for system control accounts).
	AllowNegative bool `bson:"allowNegative" json:"allowNegative"`

	// OwnerRef optionally links the account to a specific owner entity
	// (e.g., user or sub-entity within the organization).
	OwnerRef *bson.ObjectID `bson:"ownerRef,omitempty" json:"ownerRef,omitempty"`

	// Metadata holds additional arbitrary key-value attributes.
	Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
}

// Collection implements storable.Storable.
func (*LedgerAccount) Collection() string {
	return mservice.LedgerAccounts
}

// Validate enforces scope-specific invariants for ledger accounts.
func (a *LedgerAccount) Validate() error {
	if a == nil {
		return merrors.InvalidArgument("ledger account is required")
	}

	switch a.Scope {
	case LedgerAccountScopeOrganization:
		if a.OrganizationRef == nil || a.OrganizationRef.IsZero() {
			return merrors.InvalidArgument("organization_ref is required for organization accounts")
		}
		if strings.TrimSpace(string(a.Role)) == "" {
			return merrors.InvalidArgument("role is required for organization accounts")
		}
		if a.SystemPurpose != nil {
			return merrors.InvalidArgument("system_purpose must be nil for organization accounts")
		}
	case LedgerAccountScopeSystem:
		if a.OrganizationRef != nil && !a.OrganizationRef.IsZero() {
			return merrors.InvalidArgument("organization_ref must be nil for system accounts")
		}
		if strings.TrimSpace(string(a.Role)) != "" {
			return merrors.InvalidArgument("role must be empty for system accounts")
		}
		if a.SystemPurpose == nil {
			return merrors.InvalidArgument("system_purpose is required for system accounts")
		}
	default:
		return merrors.InvalidArgument("scope is required")
	}

	return nil
}
