package models

import (
	"database/sql"
	"errors"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

func (m *SaUser) BeforeCreate(tx *gorm.DB) error {
	return ensureIdentity(&m.Identity)
}

func (m *SaProject) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.OwnerID, &m.OwnerIdentity)
}

func (m *SaDatasetSource) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if m.OwnerID == 0 {
		return errors.New("dataset source owner is required")
	}
	return resolveIdentity(tx, &SaUser{}, m.OwnerID, &m.OwnerIdentity)
}

func (m *SaDatasetItem) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaDatasetSource{}, m.SourceID, &m.SourceIdentity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity)
}

func (m *SaDatasetCron) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaDatasetSource{}, m.SourceID, &m.SourceIdentity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity)
}

func (m *SaInboxItem) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity)
}

func (m *SaInboxSuggestion) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaInboxItem{}, m.InboxItemID, &m.InboxItemIdentity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity)
}

func (m *SaTask) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity); err != nil {
		return err
	}
	if err := resolveOptionalIdentity(tx, &SaUser{}, m.AssigneeID, &m.AssigneeIdentity); err != nil {
		return err
	}
	if err := resolveOptionalIdentity(tx, &SaTag{}, m.TagID, &m.TagIdentity); err != nil {
		return err
	}
	return resolveOptionalIdentity(tx, &SaInboxItem{}, m.SourceInboxItemID, &m.SourceInboxItemIdentity)
}

func (m *SaNote) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity); err != nil {
		return err
	}
	return resolveOptionalIdentity(tx, &SaInboxItem{}, m.SourceInboxItemID, &m.SourceInboxItemIdentity)
}

func (m *SaSource) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity); err != nil {
		return err
	}
	return resolveOptionalIdentity(tx, &SaInboxItem{}, m.SourceInboxItemID, &m.SourceInboxItemIdentity)
}

func (m *SaAISession) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity); err != nil {
		return err
	}
	return resolveOptionalIdentity(tx, &SaAIExpertItem{}, m.ExpertID, &m.ExpertIdentity)
}

func (m *SaAIExpertCategory) BeforeCreate(_ *gorm.DB) error {
	return ensureIdentity(&m.Identity)
}

func (m *SaAIExpertItem) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaAIExpertCategory{}, m.CategoryID, &m.CategoryIdentity)
}

func (m *SaTag) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity)
}

func (m *SaProjectChannel) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity)
}

func (m *SaCronPlan) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.CreatedBy, &m.CreatedByIdentity)
}

func (m *SaProjectEvent) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaProject{}, m.ProjectID, &m.ProjectIdentity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaUser{}, m.ActorID, &m.ActorIdentity); err != nil {
		return err
	}
	return resolveEntityIdentity(tx, m.EntityType, m.EntityID, &m.EntityIdentity)
}

func (m *SaAIKey) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.UserID, &m.UserIdentity)
}

func (m *SaAICallLog) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	return resolveIdentity(tx, &SaUser{}, m.UserID, &m.UserIdentity)
}

func (m *SaTaskShare) BeforeCreate(tx *gorm.DB) error {
	if err := ensureIdentity(&m.Identity); err != nil {
		return err
	}
	if err := resolveIdentity(tx, &SaTask{}, m.TaskID, &m.TaskIdentity); err != nil {
		return err
	}
	return resolveEntityIdentity(tx, m.ObjectType, m.ObjectID, &m.ObjectIdentity)
}

func ensureIdentity(identity *string) error {
	if *identity != "" {
		return nil
	}
	next, err := uuidV7()
	if err != nil {
		return err
	}
	*identity = next
	return nil
}

func resolveIdentity(tx *gorm.DB, model any, id uint, identity *string) error {
	if id == 0 || *identity != "" {
		return nil
	}
	resolved, err := lookupIdentity(tx, model, id)
	if err != nil {
		return err
	}
	*identity = resolved
	return nil
}

func resolveOptionalIdentity(tx *gorm.DB, model any, id *uint, identity **string) error {
	if id == nil || *id == 0 || (identity != nil && *identity != nil && **identity != "") {
		return nil
	}
	resolved, err := lookupIdentity(tx, model, *id)
	if err != nil {
		return err
	}
	*identity = &resolved
	return nil
}

func resolveEntityIdentity(tx *gorm.DB, entityType string, entityID uint, identity *string) error {
	if entityID == 0 || *identity != "" {
		return nil
	}
	switch entityType {
	case "project":
		return resolveIdentity(tx, &SaProject{}, entityID, identity)
	case "inbox", "inbox_item":
		return resolveIdentity(tx, &SaInboxItem{}, entityID, identity)
	case "task":
		return resolveIdentity(tx, &SaTask{}, entityID, identity)
	case "note":
		return resolveIdentity(tx, &SaNote{}, entityID, identity)
	case "source":
		return resolveIdentity(tx, &SaSource{}, entityID, identity)
	case "ai_session":
		return resolveIdentity(tx, &SaAISession{}, entityID, identity)
	case "tag":
		return resolveIdentity(tx, &SaTag{}, entityID, identity)
	default:
		return nil
	}
}

func lookupIdentity(tx *gorm.DB, model any, id uint) (string, error) {
	var identity string
	err := tx.Model(model).Select("identity").Where("id = ?", id).Row().Scan(&identity)
	if err == sql.ErrNoRows {
		return "", nil
	}
	return identity, err
}

func uuidV7() (string, error) {
	next, err := uuid.NewV7()
	if err != nil {
		return "", err
	}
	return next.String(), nil
}
