package db

import (
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	invitationNotificationTypeRepository   = "repository_invitation"
	invitationNotificationTypeOrganization = "organization_invitation"
)

// MigratePendingInvitationNotifications backfills the notification inbox for
// invitations created before invitations became first-class notifications.
// It is intentionally idempotent: existing notification threads are left
// unchanged, preserving their read state and timestamps.
func MigratePendingInvitationNotifications(database *gorm.DB) error {
	if !database.Migrator().HasTable("notifications") {
		return nil
	}
	return database.Transaction(func(tx *gorm.DB) error {
		var repositoryInvitations []RepositoryInvitation
		if err := tx.Select("id", "repository_id", "invitee_id", "inviter_id").Find(&repositoryInvitations).Error; err != nil {
			return err
		}
		if err := insertRepositoryInvitationNotifications(tx, repositoryInvitations); err != nil {
			return err
		}

		var organizationInvitations []OrganizationInvitation
		if err := tx.Select("id", "invitee_id", "inviter_id").
			Where("expires_at IS NULL OR expires_at > ?", time.Now().UTC()).
			Find(&organizationInvitations).Error; err != nil {
			return err
		}
		return insertOrganizationInvitationNotifications(tx, organizationInvitations)
	})
}

func insertRepositoryInvitationNotifications(tx *gorm.DB, invitations []RepositoryInvitation) error {
	now := time.Now().UTC()
	notifications := make([]Notification, 0, len(invitations))
	for _, invitation := range invitations {
		inviterID := invitation.InviterID
		repositoryID := invitation.RepositoryID
		notifications = append(notifications, Notification{
			UserID:       invitation.InviteeID,
			ActorID:      &inviterID,
			Type:         invitationNotificationTypeRepository,
			SubjectType:  invitationNotificationTypeRepository,
			SubjectID:    invitation.ID,
			RepositoryID: &repositoryID,
			CreatedAt:    now,
			UpdatedAt:    now,
		})
	}
	return createInvitationNotifications(tx, notifications)
}

func insertOrganizationInvitationNotifications(tx *gorm.DB, invitations []OrganizationInvitation) error {
	now := time.Now().UTC()
	notifications := make([]Notification, 0, len(invitations))
	for _, invitation := range invitations {
		inviterID := invitation.InviterID
		notifications = append(notifications, Notification{
			UserID:      invitation.InviteeID,
			ActorID:     &inviterID,
			Type:        invitationNotificationTypeOrganization,
			SubjectType: invitationNotificationTypeOrganization,
			SubjectID:   invitation.ID,
			CreatedAt:   now,
			UpdatedAt:   now,
		})
	}
	return createInvitationNotifications(tx, notifications)
}

func createInvitationNotifications(tx *gorm.DB, notifications []Notification) error {
	if len(notifications) == 0 {
		return nil
	}
	return tx.Clauses(clause.OnConflict{
		Columns: []clause.Column{
			{Name: "user_id"},
			{Name: "type"},
			{Name: "subject_type"},
			{Name: "subject_id"},
		},
		DoNothing: true,
	}).CreateInBatches(&notifications, 500).Error
}
