package usecase

import (
	"context"
	"errors"
	"fmt"

	libmongo "gateway/internal/library/mongo"
	redislib "gateway/internal/library/redis"
	permcfg "gateway/internal/model/permission/config"
	permission "gateway/internal/model/permission/domain"
	domrepo "gateway/internal/model/permission/domain/repository"
	dom "gateway/internal/model/permission/domain/usecase"
	permrepo "gateway/internal/model/permission/repository"
)

// FactoryParam configures the permission module. Repositories may be
// pre-built (used by tests / cmd seed) or auto-constructed from MongoConf.
type FactoryParam struct {
	MongoConf *libmongo.Conf
	Redis     *redislib.Client
	Config    permcfg.Config

	// Optional pre-built repositories. When set, MongoConf is ignored
	// for that repository.
	Permissions     domrepo.PermissionRepository
	Roles           domrepo.RoleRepository
	RolePermissions domrepo.RolePermissionRepository
	UserRoles       domrepo.UserRoleRepository
	RoleMappings    domrepo.RoleMappingRepository

	// Optional Casbin model text (overrides Config.Casbin.ModelPath).
	CasbinModelText string
}

// Module bundles all permission usecase ports.
type Module struct {
	Permission         dom.PermissionUseCase
	Role               dom.RoleUseCase
	RolePermission     dom.RolePermissionUseCase
	UserRole           dom.UserRoleUseCase
	RoleMapping        dom.RoleMappingUseCase
	AuthorizationQuery dom.AuthorizationQueryUseCase
	RBAC               dom.RBACUseCase

	Permissions     domrepo.PermissionRepository
	Roles           domrepo.RoleRepository
	RolePermissions domrepo.RolePermissionRepository
	UserRoles       domrepo.UserRoleRepository
	RoleMappings    domrepo.RoleMappingRepository
}

// NewModuleFromParam wires the seven usecases against the configured
// repositories. Mongo is required for catalog/role/user-role/mapping;
// Redis is required for the Casbin enforcer + pub/sub broadcast.
//
// When Redis is missing, RBAC stays nil and Permission/Role mutations
// continue to work but Check() always denies. Mongo missing returns an
// error because the catalog cannot live anywhere else.
func NewModuleFromParam(param FactoryParam) (*Module, error) {
	cfg := param.Config.Defaults()
	mod := &Module{
		Permissions:     param.Permissions,
		Roles:           param.Roles,
		RolePermissions: param.RolePermissions,
		UserRoles:       param.UserRoles,
		RoleMappings:    param.RoleMappings,
	}

	if mod.Permissions == nil {
		if param.MongoConf == nil || param.MongoConf.Host == "" {
			return nil, fmt.Errorf("permission: mongo config required")
		}
		mod.Permissions = permrepo.NewPermissionRepository(permrepo.PermissionRepositoryParam{Conf: param.MongoConf})
	}
	if mod.Roles == nil {
		mod.Roles = permrepo.NewRoleRepository(permrepo.RoleRepositoryParam{Conf: param.MongoConf})
	}
	if mod.RolePermissions == nil {
		mod.RolePermissions = permrepo.NewRolePermissionRepository(permrepo.RolePermissionRepositoryParam{Conf: param.MongoConf})
	}
	if mod.UserRoles == nil {
		mod.UserRoles = permrepo.NewUserRoleRepository(permrepo.UserRoleRepositoryParam{Conf: param.MongoConf})
	}
	if mod.RoleMappings == nil {
		mod.RoleMappings = permrepo.NewRoleMappingRepository(permrepo.RoleMappingRepositoryParam{Conf: param.MongoConf})
	}

	mod.Permission = NewPermissionUseCase(PermissionUseCaseParam{Permissions: mod.Permissions})

	var reloader PolicyReloader
	if cfg.Casbin.Enabled && param.Redis != nil && param.Redis.Zero() != nil {
		// Plug the bridge so rbac_usecase can build a Redis adapter
		// without importing repository (avoids cycle).
		RedisAdapterFactory = func(client *redislib.Client) (domrepo.CasbinPolicyAdapter, error) {
			if client == nil || client.Zero() == nil {
				return nil, nil
			}
			return permrepo.NewCasbinRedisAdapter(client)
		}
		rbacUC, err := NewRBACUseCase(RBACUseCaseParam{
			Roles:           mod.Roles,
			Permissions:     mod.Permissions,
			RolePermissions: mod.RolePermissions,
			UserRoles:       mod.UserRoles,
			Redis:           param.Redis,
			ModelPath:       cfg.Casbin.ModelPath,
			CasbinModelText: param.CasbinModelText,
			ReloadChannel:   cfg.Reload.Channel,
		})
		if err != nil && !errors.Is(err, permission.ErrCasbinNotConfigured) {
			return nil, err
		}
		mod.RBAC = rbacUC
		if rbacUC != nil {
			reloader = rbacUC.BroadcastReload
		}
	}

	mod.Role = NewRoleUseCase(RoleUseCaseParam{
		Roles:           mod.Roles,
		RolePermissions: mod.RolePermissions,
		UserRoles:       mod.UserRoles,
	})
	mod.RolePermission = NewRolePermissionUseCase(RolePermissionUseCaseParam{
		Roles:           mod.Roles,
		Permissions:     mod.Permissions,
		RolePermissions: mod.RolePermissions,
		Reloader:        reloader,
	})
	mod.UserRole = NewUserRoleUseCase(UserRoleUseCaseParam{
		Roles:     mod.Roles,
		UserRoles: mod.UserRoles,
		Reloader:  reloader,
	})
	mod.RoleMapping = NewRoleMappingUseCase(RoleMappingUseCaseParam{
		Roles:    mod.Roles,
		Mappings: mod.RoleMappings,
	})
	mod.AuthorizationQuery = NewAuthorizationQueryUseCase(AuthorizationQueryUseCaseParam{
		Roles:           mod.Roles,
		Permissions:     mod.Permissions,
		RolePermissions: mod.RolePermissions,
		UserRoles:       mod.UserRoles,
	})

	return mod, nil
}

// StartBackground starts the policy reload subscriber when configured.
// Safe to call when RBAC is nil (no-op).
func (m *Module) StartBackground(ctx context.Context) error {
	if m == nil || m.RBAC == nil {
		return nil
	}
	return m.RBAC.StartReloadSubscriber(ctx)
}

// StopBackground tears down the subscriber. Safe to call when never
// started.
func (m *Module) StopBackground() {
	if m == nil || m.RBAC == nil {
		return
	}
	m.RBAC.StopReloadSubscriber()
}
