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 { UserType } from 'src/user/schema/users.schema';
import { OPEN_STAGES } from 'src/lead/schema/lead.schema';
import { PolicyStatus } from 'src/policy/schema/policy.schema';
import { formatPolicy, POLICY_POPULATE } from 'src/policy/policy.format';
import { CreateCustomerDto, CustomerListDto, UpdateCustomerDto } from './dto/customer.dto';

@Injectable()
export class CustomerService {
    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 fail(key: string, lang: string, status: HttpStatus = HttpStatus.BAD_REQUEST): never {
        throw new HttpException({ message: this.t(key, lang) }, status);
    }

    /** Policy Staff only see leads assigned to them; everyone else sees all. */
    private leadScope(actor: any): Record<string, any> {
        return actor.user_type === UserType.POLICY_STAFF ? { assigned_to: actor._id } : {};
    }

    private toDto(customer: any, stats?: { leads_count: number; open_leads: number; last_activity_at: any; active_policies?: number; total_premium?: number }) {
        const { _id, name, phone_no, country_code, email, address, created_at, updated_at } = customer;
        return {
            _id, name, phone_no, country_code, email, address,
            leads_count: stats?.leads_count ?? 0,
            open_leads: stats?.open_leads ?? 0,
            active_policies: stats?.active_policies ?? 0,
            total_premium: stats?.total_premium ?? 0,
            last_activity_at: stats?.last_activity_at && stats.last_activity_at > updated_at ? stats.last_activity_at : updated_at,
            created_at
        };
    }

    async create(dto: CreateCustomerDto, actor: any, lang: string) {
        const country_code = dto.country_code || '+91';
        if (await this.models.CustomerModel.exists({ country_code, phone_no: dto.phone_no })) {
            this.fail('CUSTOMER_PHONE_EXISTS', lang);
        }

        const customer = await this.models.CustomerModel.create({
            name: dto.name, phone_no: dto.phone_no, country_code,
            email: dto.email || null, address: dto.address || null, created_by: actor._id
        });

        return this.common.successResponse(this.t('CUSTOMER_CREATED', lang), this.toDto(customer));
    }

    async list(query: CustomerListDto, actor: any, lang: string) {
        const { page, limit, search } = query;
        const filter: any = {};
        if (search) {
            const regex = { $regex: search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), $options: 'i' };
            filter.$or = [{ name: regex }, { phone_no: regex }, { email: regex }];
        }

        const [total, customers] = await Promise.all([
            this.models.CustomerModel.countDocuments(filter),
            this.models.CustomerModel.find(filter).sort({ created_at: -1 }).skip((page - 1) * limit).limit(limit).lean()
        ]);

        const stats = await this.models.LeadModel.aggregate([
            { $match: { customer_id: { $in: customers.map(c => c._id) }, ...this.leadScope(actor) } },
            {
                $group: {
                    _id: '$customer_id',
                    leads_count: { $sum: 1 },
                    open_leads: { $sum: { $cond: [{ $in: ['$stage', OPEN_STAGES] }, 1, 0] } },
                    last_activity_at: { $max: '$updated_at' }
                }
            }
        ]);
        const byCustomer = new Map(stats.map(s => [s._id.toString(), s]));

        const policyStats = await this.models.PolicyModel.aggregate([
            {
                $match: {
                    customer_id: { $in: customers.map(c => c._id) },
                    status: PolicyStatus.ACTIVE,
                    expiry_date: { $gte: +new Date() },
                    ...this.leadScope(actor)
                }
            },
            { $group: { _id: '$customer_id', active_policies: { $sum: 1 }, total_premium: { $sum: '$gross_premium' } } }
        ]);
        const policiesByCustomer = new Map(policyStats.map(s => [s._id.toString(), s]));

        return this.common.paginatedResponse(
            this.t('CUSTOMERS_FETCHED', lang),
            customers.map(c => this.toDto(c, { ...byCustomer.get(c._id.toString()), ...policiesByCustomer.get(c._id.toString()) })),
            total, page, limit
        );
    }

    async getById(id: string, actor: any, lang: string) {
        const customer = Types.ObjectId.isValid(id) ? await this.models.CustomerModel.findById(id).lean() : null;
        if (!customer) this.fail('CUSTOMER_NOT_FOUND', lang, HttpStatus.NOT_FOUND);

        const leads = await this.models.LeadModel
            .find({ customer_id: customer!._id, ...this.leadScope(actor) })
            .populate('category_id', 'name')
            .populate('assigned_to', 'name user_type')
            .sort({ created_at: -1 })
            .select('-history')
            .lean();

        const policies = await this.models.PolicyModel
            .find({ customer_id: customer!._id, ...this.leadScope(actor) })
            .populate(POLICY_POPULATE)
            .sort({ created_at: -1 })
            .lean();
        const activePolicies = policies.filter(p => p.status === PolicyStatus.ACTIVE && p.expiry_date >= +new Date());

        const stats = {
            active_policies: activePolicies.length,
            total_premium: activePolicies.reduce((sum, p) => sum + p.gross_premium, 0),
            leads_count: leads.length,
            open_leads: leads.filter(l => OPEN_STAGES.includes(l.stage)).length,
            last_activity_at: leads.reduce((max: any, l: any) => (!max || l.updated_at > max ? l.updated_at : max), null)
        };

        return this.common.successResponse(this.t('CUSTOMER_FETCHED', lang), {
            ...this.toDto(customer, stats),
            policies: policies.map(p => formatPolicy(p, actor.user_type !== UserType.POLICY_STAFF)),
            leads: leads.map((l: any) => ({
                _id: l._id,
                stage: l.stage,
                category: l.category_id ? { _id: l.category_id._id, name: l.category_id.name } : null,
                assigned_to: l.assigned_to ? { _id: l.assigned_to._id, name: l.assigned_to.name, user_type: l.assigned_to.user_type } : null,
                estimated_premium: l.estimated_premium,
                notes: l.notes,
                created_at: l.created_at,
                updated_at: l.updated_at
            }))
        });
    }

    async update(id: string, dto: UpdateCustomerDto, lang: string) {
        const customer = Types.ObjectId.isValid(id) ? await this.models.CustomerModel.findById(id) : null;
        if (!customer) this.fail('CUSTOMER_NOT_FOUND', lang, HttpStatus.NOT_FOUND);

        const phone_no = dto.phone_no ?? customer!.phone_no;
        const country_code = dto.country_code ?? customer!.country_code;
        if ((phone_no !== customer!.phone_no || country_code !== customer!.country_code) &&
            await this.models.CustomerModel.exists({ country_code, phone_no, _id: { $ne: customer!._id } })) {
            this.fail('CUSTOMER_PHONE_EXISTS', lang);
        }

        Object.assign(customer!, Object.fromEntries(Object.entries(dto).filter(([, v]) => v !== undefined)));
        await customer!.save();

        return this.common.successResponse(this.t('CUSTOMER_UPDATED', lang), this.toDto(customer));
    }
}
