import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Types, Model } from 'mongoose';
import { Build, BuildDocument, BuildIban, BuildIbanDocument } from '@/models/build.model';
import { CreateBuildInput } from './dto/create-build.input';
import { UpdateBuildAttributeInput, UpdateBuildInput, UpdateBuildResponsibleInput } from './dto/update-build.input';
import { ListBuildResponse, ListPartialAreaResponse, ListPartialCompanyResponse, ListPartialIbanResponse } from './dto/list-build.response';
import { cleanRefArrayField } from '@/lib/getListOfModelByIDs';
import { BuildArea, BuildAreaDocument } from '@/models/build-area.model';
import { Company, CompanyDocument } from '@/models/company.model';
import { Person, PersonDocument } from '@/models/person.model';
import { BuildTypes, BuildTypesDocument } from '@/models/build-types.model';

@Injectable()
export class BuildService {

    constructor(
        @InjectModel(Build.name) private readonly buildModel: Model<BuildDocument>,
        @InjectModel(BuildArea.name) private readonly areaModel: Model<BuildAreaDocument>,
        @InjectModel(BuildIban.name) private readonly ibanModel: Model<BuildIbanDocument>,
        @InjectModel(Company.name) private readonly companyModel: Model<CompanyDocument>,
        @InjectModel(Person.name) private readonly personModel: Model<PersonDocument>,
        @InjectModel(BuildTypes.name) private readonly buildTypeModel: Model<BuildTypesDocument>,
    ) { }

    async findAll(skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListBuildResponse> {
        const query: any = {}; if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) };
        const totalCount = await this.buildModel.countDocuments(query).exec();
        const data = await this.buildModel.find(query).populate('buildType').skip(skip).limit(limit).sort(sort).exec();
        return { data, totalCount };
    }

    async findById(id: Types.ObjectId, projection?: any): Promise<BuildDocument | null> {
        return this.buildModel.findById(id, projection, { lean: false }).populate({ path: 'buildArea', select: projection?.buildArea }).exec();
    }

    async create(input: CreateBuildInput): Promise<BuildDocument> {
        const getObjectIDofToken = await this.buildTypeModel.findOne({ token: input.buildType });
        const build = new this.buildModel(input);
        if (!getObjectIDofToken?._id) { throw new Error('Build type not found') }
        const idOfBuildTypes = new Types.ObjectId(getObjectIDofToken._id)
        build.set({ buildType: idOfBuildTypes });
        await build.populate('buildType')
        return build.save()
    }

    async update(uuid: string, input: UpdateBuildInput): Promise<BuildDocument> {
        let idOfBuildTypes: Types.ObjectId | null = null;
        if (input.buildType) {
            const buildTypeDoc = await this.buildTypeModel.findOne({ token: input.buildType });
            if (!buildTypeDoc?._id) { throw new Error('Build type not found') }; idOfBuildTypes = new Types.ObjectId(buildTypeDoc._id);
        }
        const build = await this.buildModel.findById(new Types.ObjectId(uuid));
        if (!build) { throw new Error('Build not found') }
        const { info, buildType: _ignoredBuildType, ...rest } = input;
        if (Object.keys(rest).length > 0) { build.set(rest) }
        if (info) { build.set({ info: { ...(build.info as any)?.toObject?.() ?? build.info ?? {}, ...info } }) }
        if (idOfBuildTypes) { build.set({ buildType: idOfBuildTypes }) }
        const saved = await build.save(); await saved.populate('buildType'); return saved;
    }

    async delete(uuid: string): Promise<boolean> {
        const build = await this.buildModel.deleteMany({ _id: new Types.ObjectId(uuid) }); return build.deletedCount > 0;
    }

    async updateResponsible(uuid: string, input: UpdateBuildResponsibleInput): Promise<BuildDocument> {
        const build = await this.buildModel.findOne({ _id: new Types.ObjectId(uuid) }); if (!build) { throw new Error('Build not found') }; build.set({ responsible: input }); return build.save()
    }

    async updateAttribute(uuid: string, input: UpdateBuildAttributeInput): Promise<BuildDocument> {
        const build = await this.buildModel.findOne({ _id: new Types.ObjectId(uuid) }); if (!build) { throw new Error('Build not found') }; build.set({ attribute: input }); return build.save()
    }

    async findAllAreas(id: string, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListPartialAreaResponse> {
        const buildId = new Types.ObjectId(id);
        const { keptIds } = await cleanRefArrayField({ parentModel: this.buildModel, refModel: this.areaModel, parentId: buildId, fieldName: "areas", });
        if (keptIds.length === 0) { return { data: [], totalCount: 0 } }
        const query: any = { _id: { $in: keptIds } };
        if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) }
        const totalCount = await this.areaModel.countDocuments(query).exec();
        const data = await this.areaModel.find(query).skip(skip).limit(limit).sort(sort).exec();
        return { data, totalCount };
    }

    async findAllIbans(id: string, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListPartialIbanResponse> {
        const buildId = new Types.ObjectId(id);
        const { keptIds } = await cleanRefArrayField({ parentModel: this.buildModel, refModel: this.ibanModel, parentId: buildId, fieldName: "ibans", });
        if (keptIds.length === 0) { return { data: [], totalCount: 0 } }
        const query: any = { _id: { $in: keptIds } };
        if (filters && Object.keys(filters).length > 0) { Object.assign(query, filters) }
        const totalCount = await this.ibanModel.countDocuments(query).exec();
        const data = await this.ibanModel.find(query).skip(skip).limit(limit).sort(sort).exec();
        return { data, totalCount };
    }

    async findAllCompaniesAndResponsibles(id: string, skip: number, limit: number, sort?: Record<string, 1 | -1>, filters?: Record<string, any>): Promise<ListPartialCompanyResponse> {
        const buildId = new Types.ObjectId(id);
        const build = await this.buildModel.findById(buildId).lean();
        if (!build) throw new Error('Build not found');
        const responsibles = (build.responsibles || []) as { company?: string; person?: string }[];
        if (responsibles.length === 0) { return { data: [], totalCount: 0 } }
        console.dir({ responsibles }, { depth: Infinity });
        const companyIds = Array.from(new Set(responsibles.map(r => r.company).filter((id): id is string => !!id))).map(id => new Types.ObjectId(id));
        const personIds = Array.from(new Set(responsibles.map(r => r.person).filter((id): id is string => !!id))).map(id => new Types.ObjectId(id));
        console.dir({ companyIds, personIds }, { depth: Infinity });
        const t = await this.companyModel.find({ _id: { $in: companyIds } })
        const l = await this.companyModel.find({ _id: { $in: companyIds } }).lean()
        console.dir({ t, l }, { depth: Infinity })
        const [companies, persons] = await Promise.all([this.companyModel.find({ _id: { $in: companyIds } }).lean(), this.personModel.find({ _id: { $in: personIds } }).lean(),]);
        console.dir({ companies, persons }, { depth: Infinity })
        const companyMap = new Map(companies.map(c => [String(c._id), c]));
        const personMap = new Map(persons.map(p => [String(p._id), p]));
        const cleanedResponsibles: { company?: string; person?: string }[] = [];
        const resolvedList: { company?: any; person?: any }[] = [];
        for (const r of responsibles) {
            const companyId = r.company && String(r.company);
            const personId = r.person && String(r.person);
            const companyDoc = companyId ? companyMap.get(companyId) : undefined;
            const personDoc = personId ? personMap.get(personId) : undefined;
            if ((companyId && !companyDoc) || (personId && !personDoc)) { continue }
            cleanedResponsibles.push({ company: companyId, person: personId });
            resolvedList.push({ company: companyDoc, person: personDoc });
        }
        console.dir({ buildId, cleanedResponsibles, resolvedList }, { depth: Infinity });
        await this.buildModel.updateOne({ _id: buildId }, { $set: { responsibles: cleanedResponsibles } });
        const totalCount = resolvedList.length;
        const data = resolvedList.slice(skip, skip + limit);
        return { data, totalCount };
    }

    buildProjection(fields: Record<string, any>): any {
        const projection: any = {};
        for (const key in fields) {
            if (key === 'build' && typeof fields[key] === 'object') { for (const subField of Object.keys(fields[key])) { projection[`build.${subField}`] = 1 } }
            else { projection[key] = 1 }
        }
        return projection;
    }

}
