import { Injectable, NotFoundException, ForbiddenException, BadRequestException, forwardRef, Inject, OnModuleInit, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ModelConfig } from './model-config.entity';
import { CreateModelConfigDto } from './dto/create-model-config.dto';
import { UpdateModelConfigDto } from './dto/update-model-config.dto';
import { TenantService } from '../tenant/tenant.service';
import { ModelType } from '../types';
import { I18nService } from '../i18n/i18n.service';

@Injectable()
export class ModelConfigService implements OnModuleInit {
  private readonly logger = new Logger(ModelConfigService.name);
  constructor(
    @InjectRepository(ModelConfig)
    private modelConfigRepository: Repository<ModelConfig>,
    @Inject(forwardRef(() => TenantService))
    private readonly tenantService: TenantService,
    private i18nService: I18nService,
  ) { }

  async onModuleInit() {
    try {
      await this.sanitizeExistingModelIds();
    } catch (err) {
      this.logger.error(`Failed to sanitize existing model IDs: ${err.message}`);
    }
  }

  private async sanitizeExistingModelIds() {
    const models = await this.modelConfigRepository.find();
    let sanitizedCount = 0;
    for (const model of models) {
      const sanitizedId = model.modelId.trim().replace(/\s+/g, '');
      if (sanitizedId !== model.modelId) {
        this.logger.log(`Sanitizing malformed model ID for "${model.name}": "${model.modelId}" -> "${sanitizedId}"`);
        model.modelId = sanitizedId;
        await this.modelConfigRepository.save(model);
        sanitizedCount++;
      }
    }
    if (sanitizedCount > 0) {
      this.logger.log(`Successfully sanitized ${sanitizedCount} malformed model IDs.`);
    }
  }

  async create(
    createModelConfigDto: CreateModelConfigDto,
  ): Promise<ModelConfig> {
    // Sanitize modelId (remove whitespace)
    if (createModelConfigDto.modelId) {
      createModelConfigDto.modelId = createModelConfigDto.modelId.trim().replace(/\s+/g, '');
    }

    const modelConfig = this.modelConfigRepository.create(createModelConfigDto);
    return this.modelConfigRepository.save(modelConfig);
  }

  async findAll(): Promise<ModelConfig[]> {
    return this.modelConfigRepository.find();
  }

  async findOne(id: string): Promise<ModelConfig> {
    const modelConfig = await this.modelConfigRepository.findOne({ where: { id } });

    if (!modelConfig) {
      throw new NotFoundException(
        this.i18nService.formatMessage('modelConfigNotFound', { id }),
      );
    }

    // Defensive sanitization in case DB hasn't been updated yet
    if (modelConfig.modelId) {
      modelConfig.modelId = modelConfig.modelId.trim().replace(/\s+/g, '');
    }

    return modelConfig;
  }

  async findByType(type: string): Promise<ModelConfig[]> {
    return this.modelConfigRepository.find({ where: { type } });
  }

  async update(
    id: string,
    updateModelConfigDto: UpdateModelConfigDto,
  ): Promise<ModelConfig> {
    const modelConfig = await this.findOne(id);

    if (!modelConfig) {
      throw new NotFoundException(
        this.i18nService.formatMessage('modelConfigNotFound', { id }),
      );
    }

    // Models are now global, no tenant check needed.

    // Sanitize modelId (remove whitespace)
    if (updateModelConfigDto.modelId) {
      updateModelConfigDto.modelId = updateModelConfigDto.modelId.trim().replace(/\s+/g, '');
    }

    // Update the model
    const updated = this.modelConfigRepository.merge(
      modelConfig,
      updateModelConfigDto,
    );
    return this.modelConfigRepository.save(updated);
  }

  async remove(id: string): Promise<void> {
    // Only allow removing if it exists
    await this.findOne(id);
    const result = await this.modelConfigRepository.delete({ id });
    if (result.affected === 0) {
      throw new NotFoundException(this.i18nService.formatMessage('modelConfigNotFound', { id }));
    }
  }

  /**
   * Set the specified model as default
   */
  async setDefault(id: string): Promise<ModelConfig> {
    const modelConfig = await this.findOne(id);

    // Clear default flag for other models of the same type (globally)
    await this.modelConfigRepository
      .createQueryBuilder()
      .update(ModelConfig)
      .set({ isDefault: false })
      .where('type = :type', { type: modelConfig.type })
      .execute();

    modelConfig.isDefault = true;
    return this.modelConfigRepository.save(modelConfig);
  }

  /**
   * Get default model for specified type
   * Strict rule: Only return models specified in Index Chat Config, throw error if not found
   */
  async findDefaultByType(tenantId: string, type: ModelType, strict: boolean = false): Promise<ModelConfig> {
    const systemId = await this.tenantService.getSystemTenantId();

    // 1. Resolve effective tenant ID
    let effectiveTenantId = tenantId;
    if (!tenantId || tenantId === 'default') {
      effectiveTenantId = systemId;
    }

    // 2. Try to get settings for the target tenant
    const settings = await this.tenantService.getSettings(effectiveTenantId);

    // 3. Extract model ID from settings
    let modelId: string | undefined;

    const extractModelId = (s: any) => {
      if (type === ModelType.LLM) return s.selectedLLMId;
      if (type === ModelType.EMBEDDING) return s.selectedEmbeddingId;
      if (type === ModelType.RERANK) return s.selectedRerankId;
      if (type === ModelType.VISION) return s.selectedVisionId;
      return undefined;
    };

    if (settings) {
      modelId = extractModelId(settings);
    }

    // 4. Fallbacks (Disabled in strict mode)
    if (!strict) {
      // a. Try system tenant settings if not already at system level
      if (!modelId && effectiveTenantId !== systemId) {
        const systemSettings = await this.tenantService.getSettings(systemId);
        if (systemSettings) {
          modelId = extractModelId(systemSettings);
        }
      }

      // b. Try global default flag in ModelConfig table
      if (!modelId) {
        const globalDefault = await this.modelConfigRepository.findOne({
          where: { type, isDefault: true, isEnabled: true },
          order: { updatedAt: 'DESC' },
        });

        if (globalDefault) {
          this.logger.log(`Using global default model for type "${type}": ${globalDefault.name}`);
          return globalDefault;
        }
      }
    }

    // 5. Final validation
    if (!modelId) {
      const errorMsg = strict 
        ? `Model of type "${type}" is not configured for organization "${tenantId || 'system'}". Please select a model in "Index Chat Config".`
        : `Model of type "${type}" is not configured in Index Chat Config for organization "${tenantId || 'system'}". Please set the default model in Admin Settings.`;
      
      throw new BadRequestException(errorMsg);
    }

    const model = await this.modelConfigRepository.findOne({
      where: { id: modelId, isEnabled: true },
    });

    if (!model) {
      // If strict mode, don't allow fallback to ANY model
      if (strict) {
        throw new BadRequestException(
          `The configured model (ID: ${modelId}) for type "${type}" for organization "${tenantId || 'system'}" is missing or disabled.`,
        );
      }

      // If the specific configured model is missing, try to find ANY enabled model of that type
      const fallbackModel = await this.modelConfigRepository.findOne({
        where: { type, isEnabled: true },
        order: { isDefault: 'DESC', updatedAt: 'DESC' },
      });

      if (!fallbackModel) {
        throw new BadRequestException(
          `No enabled model of type "${type}" found in the system.`,
        );
      }

      this.logger.warn(
        `Configured model (ID: ${modelId}) for type "${type}" is missing or disabled. Falling back to: ${fallbackModel.name}`,
      );
      return fallbackModel;
    }

    return model;
  }
}
