package repository

import (
	"baron-sso-backend/internal/domain"
	"context"
	"errors"
	"strconv"
	"strings"
	"time"

	"gorm.io/gorm"
)

type TenantRepository interface {
	Create(ctx context.Context, tenant *domain.Tenant) error
	Update(ctx context.Context, tenant *domain.Tenant) error
	FindByID(ctx context.Context, id string) (*domain.Tenant, error)
	FindBySlug(ctx context.Context, slug string) (*domain.Tenant, error)
	FindByName(ctx context.Context, name string) (*domain.Tenant, error)
	FindByDomain(ctx context.Context, domainName string) (*domain.Tenant, error)
	FindByIDs(ctx context.Context, ids []string) ([]domain.Tenant, error)
	AddDomain(ctx context.Context, tenantID string, domainName string, verified bool) error
	List(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error)
	ListByType(ctx context.Context, tenantType string) ([]domain.Tenant, error)
	DeleteBulk(ctx context.Context, ids []string) error
}

type tenantRepository struct {
	db *gorm.DB
}

func NewTenantRepository(db *gorm.DB) TenantRepository {
	return &tenantRepository{db: db}
}

func (r *tenantRepository) Create(ctx context.Context, tenant *domain.Tenant) error {
	tenant.Slug = strings.ToLower(strings.TrimSpace(tenant.Slug))
	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if tenant.Slug != "" {
			suffix := "-deleted-" + strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
			if err := tx.Unscoped().
				Model(&domain.Tenant{}).
				Where("slug = ? AND deleted_at IS NOT NULL", tenant.Slug).
				Update("slug", gorm.Expr("slug || ?", suffix)).Error; err != nil {
				return err
			}
		}
		return tx.Create(tenant).Error
	})
}

func (r *tenantRepository) Update(ctx context.Context, tenant *domain.Tenant) error {
	return r.db.WithContext(ctx).Save(tenant).Error
}

func (r *tenantRepository) FindByID(ctx context.Context, id string) (*domain.Tenant, error) {
	var tenant domain.Tenant
	if err := r.db.WithContext(ctx).Preload("Domains").First(&tenant, "id = ?", id).Error; err != nil {
		return nil, err
	}
	return &tenant, nil
}

func (r *tenantRepository) FindBySlug(ctx context.Context, slug string) (*domain.Tenant, error) {
	var tenant domain.Tenant
	if err := r.db.WithContext(ctx).Preload("Domains").Where("slug = ?", strings.ToLower(slug)).First(&tenant).Error; err != nil {
		return nil, err
	}
	return &tenant, nil
}

func (r *tenantRepository) FindByName(ctx context.Context, name string) (*domain.Tenant, error) {
	var tenant domain.Tenant
	if err := r.db.WithContext(ctx).Preload("Domains").Where("name = ?", name).First(&tenant).Error; err != nil {
		return nil, err
	}
	return &tenant, nil
}

func (r *tenantRepository) FindByDomain(ctx context.Context, domainName string) (*domain.Tenant, error) {
	var tenantDomain domain.TenantDomain
	if err := r.db.WithContext(ctx).Where("domain = ?", domainName).First(&tenantDomain).Error; err != nil {
		return nil, err
	}

	var tenant domain.Tenant
	if err := r.db.WithContext(ctx).Preload("Domains").First(&tenant, "id = ?", tenantDomain.TenantID).Error; err != nil {
		return nil, err
	}
	return &tenant, nil
}

func (r *tenantRepository) FindByIDs(ctx context.Context, ids []string) ([]domain.Tenant, error) {
	var tenants []domain.Tenant
	if len(ids) == 0 {
		return tenants, nil
	}
	if err := r.db.WithContext(ctx).Preload("Domains").Where("id IN ?", ids).Find(&tenants).Error; err != nil {
		return nil, err
	}
	return tenants, nil
}

func (r *tenantRepository) AddDomain(ctx context.Context, tenantID string, domainName string, verified bool) error {
	var existing domain.TenantDomain
	err := r.db.WithContext(ctx).Unscoped().
		Where("tenant_id = ? AND domain = ?", tenantID, domainName).
		First(&existing).Error
	if err == nil {
		return r.db.WithContext(ctx).Unscoped().Model(&existing).Updates(map[string]any{
			"verified":   verified,
			"deleted_at": nil,
		}).Error
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}

	td := domain.TenantDomain{
		TenantID: tenantID,
		Domain:   domainName,
		Verified: verified,
	}
	return r.db.WithContext(ctx).Create(&td).Error
}

func (r *tenantRepository) List(ctx context.Context, limit, offset int, parentID string, search string) ([]domain.Tenant, int64, error) {
	var tenants []domain.Tenant
	var total int64
	db := r.db.WithContext(ctx).Model(&domain.Tenant{})

	if parentID != "" {
		db = db.Where("parent_id = ?", parentID)
	}

	if search != "" {
		searchTerm := "%" + strings.ToLower(search) + "%"
		db = db.Where("LOWER(name) LIKE ? OR LOWER(slug) LIKE ? OR LOWER(description) LIKE ?", searchTerm, searchTerm, searchTerm)
	}

	if err := db.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	if err := db.Order("created_at desc, id desc").Limit(limit).Offset(offset).Preload("Domains").Find(&tenants).Error; err != nil {
		return nil, 0, err
	}

	return tenants, total, nil
}

func (r *tenantRepository) ListByType(ctx context.Context, tenantType string) ([]domain.Tenant, error) {
	var tenants []domain.Tenant
	if err := r.db.WithContext(ctx).Where("type = ?", tenantType).Preload("Domains").Find(&tenants).Error; err != nil {
		return nil, err
	}
	return tenants, nil
}

func (r *tenantRepository) DeleteBulk(ctx context.Context, ids []string) error {
	if len(ids) == 0 {
		return nil
	}

	return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		// 1. Release slugs for all target tenants to allow reuse
		suffix := "-deleted-" + time.Now().Format("20060102150405")
		if err := tx.Model(&domain.Tenant{}).Where("id IN ?", ids).
			Update("slug", gorm.Expr("slug || ?", suffix)).Error; err != nil {
			return err
		}

		// 2. Soft delete tenants
		if err := tx.Where("id IN ?", ids).Delete(&domain.Tenant{}).Error; err != nil {
			return err
		}

		// 3. Also delete related UserGroups if any (Type USER_GROUP tenants have records in user_groups table)
		if err := tx.Where("id IN ?", ids).Delete(&domain.UserGroup{}).Error; err != nil {
			return err
		}

		return nil
	})
}
