// Copyright 2022 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package db

import (
	"context"

	"github.com/pkg/errors"
	"gorm.io/gorm"

	"gogs.io/gogs/internal/dbutil"
)

// OrgsStore is the persistent interface for organizations.
type OrgsStore interface {
	// List returns a list of organizations filtered by options.
	List(ctx context.Context, opts ListOrgsOptions) ([]*Organization, error)
	// SearchByName returns a list of organizations whose username or full name
	// matches the given keyword case-insensitively. Results are paginated by given
	// page and page size, and sorted by the given order (e.g. "id DESC"). A total
	// count of all results is also returned. If the order is not given, it's up to
	// the database to decide.
	SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error)

	// CountByUser returns the number of organizations the user is a member of.
	CountByUser(ctx context.Context, userID int64) (int64, error)
}

var Orgs OrgsStore

var _ OrgsStore = (*orgs)(nil)

type orgs struct {
	*gorm.DB
}

// NewOrgsStore returns a persistent interface for orgs with given database
// connection.
func NewOrgsStore(db *gorm.DB) OrgsStore {
	return &orgs{DB: db}
}

type ListOrgsOptions struct {
	// Filter by the membership with the given user ID.
	MemberID int64
	// Whether to include private memberships.
	IncludePrivateMembers bool
}

func (db *orgs) List(ctx context.Context, opts ListOrgsOptions) ([]*Organization, error) {
	if opts.MemberID <= 0 {
		return nil, errors.New("MemberID must be greater than 0")
	}

	/*
		Equivalent SQL for PostgreSQL:

		SELECT * FROM "org"
		JOIN org_user ON org_user.org_id = org.id
		WHERE
			org_user.uid = @memberID
		[AND org_user.is_public = @includePrivateMembers]
		ORDER BY org.id ASC
	*/
	tx := db.WithContext(ctx).
		Joins(dbutil.Quote("JOIN org_user ON org_user.org_id = %s.id", "user")).
		Where("org_user.uid = ?", opts.MemberID).
		Order(dbutil.Quote("%s.id ASC", "user"))
	if !opts.IncludePrivateMembers {
		tx = tx.Where("org_user.is_public = ?", true)
	}

	var orgs []*Organization
	return orgs, tx.Find(&orgs).Error
}

func (db *orgs) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error) {
	return searchUserByName(ctx, db.DB, UserTypeOrganization, keyword, page, pageSize, orderBy)
}

func (db *orgs) CountByUser(ctx context.Context, userID int64) (int64, error) {
	var count int64
	return count, db.WithContext(ctx).Model(&OrgUser{}).Where("uid = ?", userID).Count(&count).Error
}

type Organization = User

func (o *Organization) TableName() string {
	return "user"
}
