// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later

package licenses

import (
	"context"
	"strings"

	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
	org_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
	"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"

	"xorm.io/builder"
)

func init() {
	db.RegisterModel(new(LicensePackage))
}

// LicensePackage defines a purchasable subscription tier that determines
// what update streams a group of license keys can access.
type LicensePackage struct {
	ID          int64              `xorm:"pk autoincr"`
	OwnerID     int64              `xorm:"INDEX NOT NULL"`             // org or user that owns this package
	Name        string             `xorm:"NOT NULL"`                   // e.g. "Pro Annual", "Lifetime"
	Description string             `xorm:"TEXT"`
	DurationDays int               `xorm:"NOT NULL DEFAULT 0"`         // 0 = unlimited/lifetime
	MaxSites    int                `xorm:"NOT NULL DEFAULT 0"`         // 0 = unlimited
	DomainLockHours int            `xorm:"NOT NULL DEFAULT 0"`         // hours after first use to lock domain. 0 = immediate lock
	RepoScope   string             `xorm:"TEXT NOT NULL DEFAULT 'all'"` // "all" = org-wide, or JSON array of repo IDs
	// AllowedChannels defines which update streams keys from this package
	// can access. JSON array, e.g. ["stable","rc"]. Empty = all channels.
	AllowedChannels string         `xorm:"TEXT"`
	// DomainRestriction is a comma-separated list of allowed domains.
	// Keys generated from this package inherit this unless overridden.
	// Empty = no restriction.
	DomainRestriction string       `xorm:"TEXT"`
	IsActive    bool               `xorm:"NOT NULL DEFAULT true"`
	IsArchived  bool               `xorm:"NOT NULL DEFAULT false"`
	CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
	UpdatedUnix timeutil.TimeStamp `xorm:"UPDATED"`
}

func (LicensePackage) TableName() string {
	return "license_package"
}

// CreateLicensePackage creates a new license package.
func CreateLicensePackage(ctx context.Context, pkg *LicensePackage) error {
	_, err := db.GetEngine(ctx).Insert(pkg)
	return err
}

// GetLicensePackageByID returns a license package by ID.
func GetLicensePackageByID(ctx context.Context, id int64) (*LicensePackage, error) {
	pkg := new(LicensePackage)
	has, err := db.GetEngine(ctx).ID(id).Get(pkg)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, db.ErrNotExist{Resource: "LicensePackage", ID: id}
	}
	return pkg, nil
}

// FindLicensePackageOptions for db.Find/db.Count.
type FindLicensePackageOptions struct {
	db.ListOptions
	OwnerID int64
}

func (opts FindLicensePackageOptions) ToConds() builder.Cond {
	cond := builder.NewCond()
	if opts.OwnerID > 0 {
		cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
	}
	return cond
}

// ListLicensePackages returns active (non-archived) packages for the given owner.
func ListLicensePackages(ctx context.Context, ownerID int64) ([]*LicensePackage, error) {
	pkgs := make([]*LicensePackage, 0, 10)
	return pkgs, db.GetEngine(ctx).Where("owner_id = ? AND is_archived = ?", ownerID, false).Find(&pkgs)
}

// ListLicensePackagesWithAncestors returns packages from the org and all parent orgs.
func ListLicensePackagesWithAncestors(ctx context.Context, ownerID int64) ([]*LicensePackage, error) {
	ancestorIDs := org_model.GetAncestorOrgIDs(ctx, ownerID)
	pkgs := make([]*LicensePackage, 0, 10)
	return pkgs, db.GetEngine(ctx).In("owner_id", ancestorIDs).Where("is_archived = ?", false).Find(&pkgs)
}

// ListLicenseKeysWithAncestors returns keys from the org and all parent orgs.
func ListLicenseKeysWithAncestors(ctx context.Context, ownerID int64) ([]*LicenseKey, error) {
	ancestorIDs := org_model.GetAncestorOrgIDs(ctx, ownerID)
	keys := make([]*LicenseKey, 0, 20)
	return keys, db.GetEngine(ctx).In("owner_id", ancestorIDs).Find(&keys)
}

// SearchLicenseKeysWithAncestors searches keys across the org and all parent orgs.
func SearchLicenseKeysWithAncestors(ctx context.Context, ownerID int64, query string) ([]*LicenseKey, error) {
	ancestorIDs := org_model.GetAncestorOrgIDs(ctx, ownerID)
	keys := make([]*LicenseKey, 0, 20)
	like := "%" + strings.ToLower(query) + "%"
	return keys, db.GetEngine(ctx).
		In("owner_id", ancestorIDs).
		And("(LOWER(key_prefix) LIKE ? OR LOWER(key_raw) LIKE ? OR LOWER(licensee_name) LIKE ? OR LOWER(licensee_email) LIKE ? OR LOWER(domain_restriction) LIKE ? OR LOWER(payment_ref) LIKE ?)",
			like, like, like, like, like, like).
		Find(&keys)
}

// ListArchivedLicensePackages returns archived packages for the given owner.
func ListArchivedLicensePackages(ctx context.Context, ownerID int64) ([]*LicensePackage, error) {
	pkgs := make([]*LicensePackage, 0, 10)
	return pkgs, db.GetEngine(ctx).Where("owner_id = ? AND is_archived = ?", ownerID, true).Find(&pkgs)
}

// ArchiveLicensePackage sets a package as archived.
func ArchiveLicensePackage(ctx context.Context, id int64) error {
	_, err := db.GetEngine(ctx).ID(id).Cols("is_archived").Update(&LicensePackage{IsArchived: true})
	return err
}

// UnarchiveLicensePackage removes the archived flag from a package.
func UnarchiveLicensePackage(ctx context.Context, id int64) error {
	_, err := db.GetEngine(ctx).ID(id).Cols("is_archived").Update(&LicensePackage{IsArchived: false})
	return err
}

// UpdateLicensePackage updates a license package.
func UpdateLicensePackage(ctx context.Context, pkg *LicensePackage) error {
	_, err := db.GetEngine(ctx).ID(pkg.ID).AllCols().Update(pkg)
	return err
}

// CountOrgPackages returns the number of license packages for an organization.
func CountOrgPackages(ctx context.Context, orgID int64) (int64, error) {
	return db.GetEngine(ctx).Where("owner_id = ?", orgID).Count(new(LicensePackage))
}

// DeleteLicensePackage deletes a license package by ID.
func DeleteLicensePackage(ctx context.Context, id int64) error {
	_, err := db.GetEngine(ctx).ID(id).Delete(new(LicensePackage))
	return err
}
