import {
    BadRequestException,
    ForbiddenException,
    Injectable,
    NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Tenant } from './tenant.entity';
import { TenantSetting } from './tenant-setting.entity';
import { TenantMember } from './tenant-member.entity';
import { I18nService } from '../i18n/i18n.service';

@Injectable()
export class TenantService {
    public static readonly DEFAULT_TENANT_NAME = 'Default';

    constructor(
        @InjectRepository(Tenant)
        private readonly tenantRepository: Repository<Tenant>,
        @InjectRepository(TenantSetting)
        private readonly tenantSettingRepository: Repository<TenantSetting>,
        @InjectRepository(TenantMember)
        private readonly tenantMemberRepository: Repository<TenantMember>,
        private readonly i18nService: I18nService,
    ) { }

    async findAll(page?: number, limit?: number): Promise<{ data: Tenant[]; total: number } | Tenant[]> {
        const queryBuilder = this.tenantRepository.createQueryBuilder('tenant')
            .leftJoinAndSelect('tenant.members', 'members')
            .leftJoinAndSelect('members.user', 'user')
            .orderBy('tenant.createdAt', 'ASC');

        if (page !== undefined && limit !== undefined) {
            const [data, total] = await queryBuilder
                .skip((page - 1) * limit)
                .take(limit)
                .getManyAndCount();
            return { data, total };
        }

        return queryBuilder.getMany();
    }

    async findById(id: string): Promise<Tenant> {
        const tenant = await this.tenantRepository.findOneBy({ id });
        if (!tenant) throw new NotFoundException(this.i18nService.getMessage('tenantNotFound'));
        return tenant;
    }

    async findByName(name: string): Promise<Tenant | null> {
        return this.tenantRepository.findOneBy({ name });
    }

    async create(name: string, domain?: string, parentId?: string, isSystem: boolean = false): Promise<Tenant> {
        const tenant = this.tenantRepository.create({ name, domain, parentId, isSystem });
        return this.tenantRepository.save(tenant);
    }

    async update(id: string, data: Partial<Tenant>): Promise<Tenant> {
        await this.tenantRepository.update(id, data);
        return this.findById(id);
    }

    async remove(id: string): Promise<void> {
        await this.tenantRepository.delete(id);
    }

    async getSettings(tenantId: string): Promise<TenantSetting | null> {
        return this.tenantSettingRepository.findOneBy({ tenantId });
    }

    async updateSettings(tenantId: string, data: Partial<TenantSetting>): Promise<TenantSetting> {
        let setting = await this.tenantSettingRepository.findOneBy({ tenantId });
        if (!setting) {
            setting = this.tenantSettingRepository.create({ tenantId, ...data });
        } else {
            if (data.enabledModelIds) {
                if (setting.selectedLLMId && !data.enabledModelIds.includes(setting.selectedLLMId)) {
                    data.selectedLLMId = null as any;
                }
                if (setting.selectedEmbeddingId && !data.enabledModelIds.includes(setting.selectedEmbeddingId)) {
                    data.selectedEmbeddingId = null as any;
                }
                if (setting.selectedRerankId && !data.enabledModelIds.includes(setting.selectedRerankId)) {
                    data.selectedRerankId = null as any;
                }
                if (setting.selectedVisionId && !data.enabledModelIds.includes(setting.selectedVisionId)) {
                    data.selectedVisionId = null as any;
                }
            }
            Object.assign(setting, data);
        }
        return this.tenantSettingRepository.save(setting);
    }

    async updateMemberRole(tenantId: string, userId: string, role: string): Promise<TenantMember> {
        const existing = await this.tenantMemberRepository.findOneBy({ tenantId, userId });
        if (!existing) {
            throw new ForbiddenException(`Member not found in this organization`);
        }
        existing.role = role as any;
        return this.tenantMemberRepository.save(existing);
    }

    async addMember(tenantId: string, userId: string, role: string = 'USER'): Promise<TenantMember> {
        const existing = await this.tenantMemberRepository.findOneBy({ tenantId, userId });
        if (existing) {
            existing.role = role as any;
            return this.tenantMemberRepository.save(existing);
        }
        const member = this.tenantMemberRepository.create({ tenantId, userId, role: role as any });
        return this.tenantMemberRepository.save(member);
    }

    async removeMember(tenantId: string, userId: string): Promise<void> {
        await this.tenantMemberRepository.delete({ tenantId, userId });
    }

    async getMembers(tenantId: string, page?: number, limit?: number): Promise<{ data: TenantMember[]; total: number }> {
        const queryBuilder = this.tenantMemberRepository.createQueryBuilder('member')
            .leftJoinAndSelect('member.user', 'user')
            .where('member.tenantId = :tenantId', { tenantId })
            .select(['member', 'user.id', 'user.username', 'user.displayName', 'user.isAdmin'])
            .orderBy('member.createdAt', 'DESC');

        if (page !== undefined && limit !== undefined) {
            const [data, total] = await queryBuilder
                .skip((page - 1) * limit)
                .take(limit)
                .getManyAndCount();
            return { data, total };
        }

        const [data, total] = await queryBuilder.getManyAndCount();
        return { data, total };
    }

    /**
     * Ensure a "Default" tenant exists for data migration purposes.
     * Called during app bootstrap.
     */
    async ensureDefaultTenant(): Promise<Tenant> {
        let defaultTenant = await this.findByName(TenantService.DEFAULT_TENANT_NAME);
        if (!defaultTenant) {
            defaultTenant = await this.create(TenantService.DEFAULT_TENANT_NAME, 'default.localhost', undefined, true);
        } else if (!defaultTenant.isSystem) {
            defaultTenant.isSystem = true;
            await this.tenantRepository.save(defaultTenant);
        }
        return defaultTenant;
    }

    async getMemberIds(tenantId: string): Promise<string[]> {
        const members = await this.tenantMemberRepository.find({
            where: { tenantId },
            select: ['userId'],
        });
        return members.map(m => m.userId);
    }

    /**
     * Get the ID of the system/default tenant
     */
    async getSystemTenantId(): Promise<string> {
        const defaultTenant = await this.ensureDefaultTenant();
        return defaultTenant.id;
    }
}
