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';

@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>,
    ) { }

    async findAll(): Promise<Tenant[]> {
        return this.tenantRepository.find({
            relations: ['members', 'members.user'],
            order: { createdAt: 'ASC' }
        });
    }

    async findById(id: string): Promise<Tenant> {
        const tenant = await this.tenantRepository.findOneBy({ id });
        if (!tenant) throw new NotFoundException(`Tenant ${id} not found`);
        return tenant;
    }

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

    async create(name: string, domain?: string): Promise<Tenant> {
        const existing = await this.findByName(name);
        if (existing) throw new BadRequestException(`Tenant name "${name}" already exists`);

        const tenant = this.tenantRepository.create({ name, domain, settings: '{}' });
        const saved = await this.tenantRepository.save(tenant);

        // Auto-create default TenantSettings
        const setting = this.tenantSettingRepository.create({ tenantId: saved.id });
        await this.tenantSettingRepository.save(setting);

        return saved;
    }

    async update(id: string, data: Partial<Tenant>): Promise<Tenant> {
        const tenant = await this.findById(id);
        if (tenant.name === TenantService.DEFAULT_TENANT_NAME) {
            throw new ForbiddenException(`Cannot modify the "${TenantService.DEFAULT_TENANT_NAME}" organization`);
        }
        await this.tenantRepository.update(id, data);
        return this.findById(id);
    }

    async remove(id: string): Promise<void> {
        const tenant = await this.findById(id);
        if (tenant.name === TenantService.DEFAULT_TENANT_NAME) {
            throw new ForbiddenException(`Cannot delete the "${TenantService.DEFAULT_TENANT_NAME}" organization`);
        }
        await this.tenantRepository.delete(id);
    }

    async getSettings(tenantId: string): Promise<TenantSetting> {
        let setting = await this.tenantSettingRepository.findOneBy({ tenantId });
        if (!setting) {
            // Defensive: Check if tenant actually exists before creating settings
            // to avoid FOREIGN KEY constraint failure if tenantId is invalid/legacy
            const tenantExists = await this.tenantRepository.findOneBy({ id: tenantId });
            if (!tenantExists) {
                console.warn(`[TenantService] Attempted to get settings for non-existent tenant: ${tenantId}`);
                // Return a transient default object without saving to DB
                return this.tenantSettingRepository.create({ tenantId });
            }

            setting = this.tenantSettingRepository.create({ tenantId });
            setting = await this.tenantSettingRepository.save(setting);
        }
        return setting;
    }

    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;
                }
            }
            Object.assign(setting, data);
        }
        return this.tenantSettingRepository.save(setting);
    }

    async addMember(tenantId: string, userId: string, role: string = 'USER'): Promise<TenantMember> {
        const tenant = await this.findById(tenantId);
        if (tenant.name === TenantService.DEFAULT_TENANT_NAME) {
            throw new ForbiddenException(`Cannot manually bind members to the "${TenantService.DEFAULT_TENANT_NAME}" organization`);
        }
        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): Promise<TenantMember[]> {
        return this.tenantMemberRepository.find({
            where: { tenantId },
            relations: ['user'],
        });
    }

    /**
     * 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');
        }
        return defaultTenant;
    }
}
