import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Types } from 'mongoose';
import { ModelsService } from 'src/models/models.service';
import { CommonService } from 'src/common/common.service';
import { TranslationService } from 'src/common/services/translation.service';
import { CreateInsurerDto, InsurerListDto, UpdateInsurerDto } from './dto/insurer.dto';

@Injectable()
export class InsurerService {
    constructor(
        private readonly models: ModelsService,
        private readonly common: CommonService,
        private readonly translationService: TranslationService
    ) { }

    private t(key: string, lang: string) {
        return this.translationService.translate(key, lang);
    }

    private toObjectId(id: string, lang: string) {
        if (!Types.ObjectId.isValid(id)) {
            throw new HttpException({ message: this.t('INSURER_NOT_FOUND', lang) }, HttpStatus.NOT_FOUND);
        }
        return new Types.ObjectId(id);
    }

    private async assertNameFree(name: string, lang: string, excludeId?: Types.ObjectId) {
        const filter: any = { name };
        if (excludeId) filter._id = { $ne: excludeId };
        const existing = await this.models.InsurerModel.findOne(filter).collation({ locale: 'en', strength: 2 }).lean();
        if (existing) {
            throw new HttpException({ message: this.t('INSURER_ALREADY_EXISTS', lang) }, HttpStatus.BAD_REQUEST);
        }
    }

    /** Every id must be an existing category; ids not already on the insurer must also be active. */
    private async resolveCategoryIds(ids: string[], lang: string, current: Types.ObjectId[] = []) {
        const objectIds = ids.map(id => new Types.ObjectId(id));
        const currentSet = new Set(current.map(id => id.toString()));
        const categories = await this.models.CategoryModel.find({ _id: { $in: objectIds } }).lean();

        const valid = categories.length === objectIds.length &&
            categories.every(c => c.is_active || currentSet.has(c._id.toString()));
        if (!valid) {
            throw new HttpException({ message: this.t('INVALID_CATEGORIES', lang) }, HttpStatus.BAD_REQUEST);
        }
        return objectIds;
    }

    private format(insurer: any) {
        const { category_ids, ...rest } = insurer;
        return {
            ...rest,
            categories: (category_ids || []).map((c: any) => ({ _id: c._id, name: c.name, is_active: c.is_active })),
        };
    }

    private async findFormatted(id: Types.ObjectId) {
        const insurer = await this.models.InsurerModel.findById(id)
            .populate('category_ids', 'name is_active')
            .lean();
        return insurer ? this.format(insurer) : null;
    }

    async create(dto: CreateInsurerDto, lang: string) {
        await this.assertNameFree(dto.name, lang);
        const category_ids = await this.resolveCategoryIds(dto.category_ids || [], lang);

        const insurer = await this.models.InsurerModel.create({
            name: dto.name,
            claims_helpline: dto.claims_helpline || null,
            category_ids,
        });

        return this.common.successResponse(this.t('INSURER_CREATED', lang), await this.findFormatted(insurer._id as Types.ObjectId));
    }

    async list(query: InsurerListDto, lang: string) {
        const { page, limit, search, is_active, category_id } = query;
        const filter: any = {};
        if (search) {
            const regex = { $regex: search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), $options: 'i' };
            filter.$or = [{ name: regex }, { claims_helpline: regex }];
        }
        if (is_active !== undefined) filter.is_active = is_active === 'true';
        if (category_id) filter.category_ids = new Types.ObjectId(category_id);

        const [total, insurers] = await Promise.all([
            this.models.InsurerModel.countDocuments(filter),
            this.models.InsurerModel.find(filter)
                .populate('category_ids', 'name is_active')
                .sort({ created_at: 1 })
                .skip((page - 1) * limit)
                .limit(limit)
                .lean(),
        ]);

        return this.common.paginatedResponse(this.t('INSURERS_FETCHED', lang), insurers.map(i => this.format(i)), total, page, limit);
    }

    async getById(id: string, lang: string) {
        const insurer = await this.findFormatted(this.toObjectId(id, lang));
        if (!insurer) {
            throw new HttpException({ message: this.t('INSURER_NOT_FOUND', lang) }, HttpStatus.NOT_FOUND);
        }
        return this.common.successResponse(this.t('INSURER_FETCHED', lang), insurer);
    }

    async update(id: string, dto: UpdateInsurerDto, lang: string) {
        const _id = this.toObjectId(id, lang);
        const insurer = await this.models.InsurerModel.findById(_id);
        if (!insurer) {
            throw new HttpException({ message: this.t('INSURER_NOT_FOUND', lang) }, HttpStatus.NOT_FOUND);
        }

        if (dto.name !== undefined && dto.name.toLowerCase() !== insurer.name.toLowerCase()) {
            await this.assertNameFree(dto.name, lang, _id);
        }
        if (dto.category_ids !== undefined) {
            insurer.category_ids = await this.resolveCategoryIds(dto.category_ids, lang, insurer.category_ids);
        }
        if (dto.name !== undefined) insurer.name = dto.name;
        if (dto.claims_helpline !== undefined) insurer.claims_helpline = dto.claims_helpline;
        if (dto.is_active !== undefined) insurer.is_active = dto.is_active;
        await insurer.save();

        return this.common.successResponse(this.t('INSURER_UPDATED', lang), await this.findFormatted(_id));
    }
}
