// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT

package user

import (
	"context"

	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/timeutil"
)

// PinnedRepo represents a pinned repository for a user's profile
type PinnedRepo struct {
	ID           int64              `xorm:"pk autoincr"`
	UserID       int64              `xorm:"INDEX NOT NULL"`
	RepoID       int64              `xorm:"INDEX NOT NULL"`
	DisplayOrder int                `xorm:"DEFAULT 0"`
	CreatedUnix  timeutil.TimeStamp `xorm:"created"`

	Repo any `xorm:"-"` // Will be loaded by caller to avoid import cycle
}

// TableName returns the table name for PinnedRepo
func (p *PinnedRepo) TableName() string {
	return "user_pinned_repo"
}

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

// MaxPinnedRepos is the maximum number of repos a user can pin
const MaxPinnedRepos = 6

// GetPinnedRepos returns all pinned repos for a user
func GetPinnedRepos(ctx context.Context, userID int64) ([]*PinnedRepo, error) {
	pinnedRepos := make([]*PinnedRepo, 0, MaxPinnedRepos)
	err := db.GetEngine(ctx).
		Where("user_id = ?", userID).
		OrderBy("display_order ASC, id ASC").
		Find(&pinnedRepos)
	return pinnedRepos, err
}

// CountPinnedRepos returns the count of pinned repos for a user
func CountPinnedRepos(ctx context.Context, userID int64) (int64, error) {
	return db.GetEngine(ctx).Where("user_id = ?", userID).Count(new(PinnedRepo))
}

// IsRepoPinnedByUser checks if a repo is pinned by a user
func IsRepoPinnedByUser(ctx context.Context, userID, repoID int64) (bool, error) {
	return db.GetEngine(ctx).Where("user_id = ? AND repo_id = ?", userID, repoID).Exist(new(PinnedRepo))
}

// PinRepoToUser pins a repo to a user's profile
func PinRepoToUser(ctx context.Context, userID, repoID int64) error {
	// Check if already pinned
	exists, err := IsRepoPinnedByUser(ctx, userID, repoID)
	if err != nil {
		return err
	}
	if exists {
		return nil // Already pinned
	}

	// Check max limit
	count, err := CountPinnedRepos(ctx, userID)
	if err != nil {
		return err
	}
	if count >= MaxPinnedRepos {
		return ErrPinnedRepoLimit{UserID: userID, Limit: MaxPinnedRepos}
	}

	// Get next display order
	var maxOrder int
	_, err = db.GetEngine(ctx).
		Table("user_pinned_repo").
		Where("user_id = ?", userID).
		Select("COALESCE(MAX(display_order), 0)").
		Get(&maxOrder)
	if err != nil {
		return err
	}

	pinnedRepo := &PinnedRepo{
		UserID:       userID,
		RepoID:       repoID,
		DisplayOrder: maxOrder + 1,
	}

	_, err = db.GetEngine(ctx).Insert(pinnedRepo)
	return err
}

// UnpinRepoFromUser unpins a repo from a user's profile
func UnpinRepoFromUser(ctx context.Context, userID, repoID int64) error {
	_, err := db.GetEngine(ctx).Where("user_id = ? AND repo_id = ?", userID, repoID).Delete(new(PinnedRepo))
	return err
}

// UpdatePinnedRepoOrder updates the display order of pinned repos
func UpdatePinnedRepoOrder(ctx context.Context, userID int64, repoIDs []int64) error {
	for i, repoID := range repoIDs {
		_, err := db.GetEngine(ctx).
			Where("user_id = ? AND repo_id = ?", userID, repoID).
			Cols("display_order").
			Update(&PinnedRepo{DisplayOrder: i})
		if err != nil {
			return err
		}
	}
	return nil
}

// DeletePinnedReposByRepoID deletes all pins for a repo (when repo is deleted)
func DeletePinnedReposByRepoID(ctx context.Context, repoID int64) error {
	_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(PinnedRepo))
	return err
}

// ErrPinnedRepoLimit represents an error when user has reached pin limit
type ErrPinnedRepoLimit struct {
	UserID int64
	Limit  int
}

func (err ErrPinnedRepoLimit) Error() string {
	return "user has reached the maximum number of pinned repositories"
}

// IsErrPinnedRepoLimit checks if error is ErrPinnedRepoLimit
func IsErrPinnedRepoLimit(err error) bool {
	_, ok := err.(ErrPinnedRepoLimit)
	return ok
}
