package ledger

import (
	"regexp"
	"strconv"
	"strings"
	"time"

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

// AccountType controls normal balance side.
type AccountType string

const (
	AccountTypeAsset     AccountType = "asset"
	AccountTypeLiability AccountType = "liability"
	AccountTypeRevenue   AccountType = "revenue"
	AccountTypeExpense   AccountType = "expense"
)

// lowercase a-z0-9 segments separated by ':'
var accountKeyRe = regexp.MustCompile(`^[a-z0-9]+(?:[:][a-z0-9]+)*$`)

type Account struct {
	model.PermissionBound `bson:",inline" json:",inline"`

	// Immutable identifier used by postings, balances, etc.
	AccountKey string   `bson:"accountKey"            json:"accountKey"`          // e.g., "asset:cash:operating"
	PathParts  []string `bson:"pathParts,omitempty"   json:"pathParts,omitempty"` // optional: ["asset","cash","operating"]

	// Classification
	AccountType AccountType `bson:"accountType" json:"accountType"`
	Currency    string      `bson:"currency,omitempty" json:"currency,omitempty"`

	// Managing entity in your platform (not legal owner).
	OrganizationRef *bson.ObjectID `bson:"organizationRef,omitempty" json:"organizationRef,omitempty"`

	// Posting policy & lifecycle
	AllowNegative bool                      `bson:"allowNegative" json:"allowNegative"`
	Status        model.LedgerAccountStatus `bson:"status"        json:"status"`
	Role          account_role.AccountRole  `bson:"role,omitempty" json:"role,omitempty"`

	// Legal ownership history
	Ownerships    []Ownership `bson:"ownerships,omitempty"    json:"ownerships,omitempty"`
	CurrentOwners []Ownership `bson:"currentOwners,omitempty" json:"currentOwners,omitempty"` // denormalized cache

}

func (a *Account) NormalizeKey() {
	a.AccountKey = strings.TrimSpace(strings.ToLower(a.AccountKey))
	if len(a.PathParts) == 0 && a.AccountKey != "" {
		a.PathParts = strings.Split(a.AccountKey, ":")
	}
}

func (a *Account) Validate() error {
	var verr *ValidationError

	if strings.TrimSpace(a.AccountKey) == "" {
		veAdd(&verr, "accountKey", "required", "accountKey is required")
	} else if !accountKeyRe.MatchString(a.AccountKey) {
		veAdd(&verr, "accountKey", "invalid_format", "use lowercase a-z0-9 segments separated by ':'")
	}

	switch a.AccountType {
	case AccountTypeAsset, AccountTypeLiability, AccountTypeRevenue, AccountTypeExpense:
	default:
		veAdd(&verr, "accountType", "invalid", "expected asset|liability|revenue|expense")
	}

	switch a.Status {
	case model.LedgerAccountStatusActive, model.LedgerAccountStatusFrozen, model.LedgerAccountStatusClosed:
	default:
		veAdd(&verr, "status", "invalid", "expected active|frozen|closed")
	}

	if role := strings.TrimSpace(string(a.Role)); role != "" {
		switch a.Role {
		case account_role.AccountRoleOperating,
			account_role.AccountRoleHold,
			account_role.AccountRoleTransit,
			account_role.AccountRoleSettlement,
			account_role.AccountRoleClearing,
			account_role.AccountRolePending,
			account_role.AccountRoleReserve,
			account_role.AccountRoleLiquidity,
			account_role.AccountRoleFee,
			account_role.AccountRoleChargeback,
			account_role.AccountRoleAdjustment:
		default:
			veAdd(&verr, "role", "invalid", "unknown account role")
		}
	}

	// Validate ownership arrays with index context
	for i := range a.Ownerships {
		if err := a.Ownerships[i].Validate(); err != nil {
			veAdd(&verr, "ownerships["+strconv.Itoa(i)+"]", "invalid", err.Error())
		}
	}
	for i := range a.CurrentOwners {
		if err := a.CurrentOwners[i].Validate(); err != nil {
			veAdd(&verr, "currentOwners["+strconv.Itoa(i)+"]", "invalid", err.Error())
		}
	}
	if verr == nil {
		return nil
	}
	return verr
}

// ResolveCurrentOwners recomputes CurrentOwners for a given moment.
func (a *Account) ResolveCurrentOwners(asOf time.Time) {
	dst := dstSlice(a.CurrentOwners, 0, len(a.Ownerships))
	for _, o := range a.Ownerships {
		if o.ActiveAt(asOf) {
			dst = append(dst, o)
		}
	}
	a.CurrentOwners = dst
}

// BalanceSide returns +1 for debit-normal (asset, expense), -1 for credit-normal (liability, revenue).
func (a *Account) BalanceSide() int {
	switch a.AccountType {
	case AccountTypeAsset, AccountTypeExpense:
		return +1
	default:
		return -1
	}
}

// CloseOwnershipPeriod sets the To date for the first matching active ownership.
func (a *Account) CloseOwnershipPeriod(partyID bson.ObjectID, role OwnershipRole, to time.Time) bool {
	for i := range a.Ownerships {
		o := &a.Ownerships[i]
		if o.OwnerPartyRef == partyID && o.Role == role && o.ActiveAt(to) {
			o.To = &to
			return true
		}
	}
	return false
}
