import db from '../database/models/index.js';

const { Application, Dealer, Outlet, TerminationRequest, Resignation, RelocationRequest, ConstitutionalChange, FnF } =
    db;

export type GeographicContext = {
    asmId: string | null;
    zmId: string | null;
    rbmId: string | null;
    zbhId: string | null;
};

const districtInclude = [
    {
        model: db.District,
        as: 'district',
        include: [{ model: db.Region, as: 'region' }, { model: db.Zone, as: 'zone' }]
    }
];

function contextFromDistrict(district: any): GeographicContext | null {
    if (!district) return null;
    return {
        asmId: district.asmId || null,
        zmId: district.zmId || null,
        rbmId: district.region?.rbmId || null,
        zbhId: district.zone?.zbhId || null
    };
}

async function districtFromDealer(dealerId: string): Promise<GeographicContext | null> {
    const dealer = await Dealer.findByPk(dealerId, {
        attributes: ['id', 'applicationId', 'asmId'],
        include: [
            {
                model: Application,
                as: 'application',
                attributes: ['id'],
                include: districtInclude
            }
        ]
    });
    if (!dealer) return null;

    const fromApp = contextFromDistrict((dealer as any).application?.district);
    if (fromApp) return fromApp;

    if (dealer.asmId) {
        return { asmId: dealer.asmId, zmId: null, rbmId: null, zbhId: null };
    }

    const dealerUser = await db.User.findOne({
        where: { dealerId: dealer.id },
        attributes: ['id']
    });
    if (dealerUser) {
        const outlet = await Outlet.findOne({
            where: { dealerId: dealerUser.id },
            attributes: ['id'],
            include: districtInclude
        });
        const fromOutlet = contextFromDistrict((outlet as any)?.district);
        if (fromOutlet) return fromOutlet;
    }

    return null;
}

async function districtFromOutlet(outletId: string): Promise<GeographicContext | null> {
    const outlet = await Outlet.findByPk(outletId, {
        attributes: ['id'],
        include: districtInclude
    });
    return contextFromDistrict((outlet as any)?.district);
}

/**
 * Resolve ASM / DD-ZM / RBM / ZBH user IDs for an SLA track from dealer geography (SRS §9.4.3).
 */
export async function resolveGeographicContext(track: {
    entityType: string;
    entityId: string;
    applicationId?: string | null;
}): Promise<GeographicContext | null> {
    if (track.entityType === 'application' && track.applicationId) {
        const application = await Application.findByPk(track.applicationId, {
            attributes: ['id'],
            include: districtInclude
        });
        return contextFromDistrict((application as any)?.district);
    }

    if (track.entityType === 'termination') {
        const row = await TerminationRequest.findByPk(track.entityId, { attributes: ['dealerId'] });
        if (!row?.dealerId) return null;
        return districtFromDealer(row.dealerId);
    }

    if (track.entityType === 'resignation') {
        const row = await Resignation.findByPk(track.entityId, { attributes: ['outletId', 'dealerId'] });
        if (row?.outletId) return districtFromOutlet(row.outletId);
        if (row?.dealerId) return districtFromDealer(row.dealerId);
        return null;
    }

    if (track.entityType === 'relocation') {
        const row = await RelocationRequest.findByPk(track.entityId, { attributes: ['outletId'] });
        if (!row?.outletId) return null;
        return districtFromOutlet(row.outletId);
    }

    if (track.entityType === 'constitutional') {
        const row = await ConstitutionalChange.findByPk(track.entityId, { attributes: ['outletId'] });
        if (row?.outletId) return districtFromOutlet(row.outletId);
        return null;
    }

    if (track.entityType === 'fnf') {
        const fnf = await FnF.findByPk(track.entityId, {
            attributes: ['terminationRequestId', 'resignationId']
        });
        if (fnf?.terminationRequestId) {
            const term = await TerminationRequest.findByPk(fnf.terminationRequestId, { attributes: ['dealerId'] });
            if (term?.dealerId) return districtFromDealer(term.dealerId);
        }
        if (fnf?.resignationId) {
            const res = await Resignation.findByPk(fnf.resignationId, { attributes: ['outletId', 'dealerId'] });
            if (res?.outletId) return districtFromOutlet(res.outletId);
            if (res?.dealerId) return districtFromDealer(res.dealerId);
        }
    }

    return null;
}

const ROLE_TO_FIELD: Record<string, keyof GeographicContext> = {
    ASM: 'asmId',
    'DD-ZM': 'zmId',
    RBM: 'rbmId',
    ZBH: 'zbhId'
};

export function userIdsForGeographicRoles(ctx: GeographicContext, roles: string[]): string[] {
    const ids = new Set<string>();
    for (const role of roles) {
        const field = ROLE_TO_FIELD[role.trim()];
        if (!field) continue;
        const uid = ctx[field];
        if (uid) ids.add(uid);
    }
    return [...ids];
}

/** Geography-aware user IDs for roles; falls back to global active users by role when unmapped. */
export async function resolveRecipientsForRoles(
    track: { entityType: string; entityId: string; applicationId?: string | null },
    roles: string[]
): Promise<string[]> {
    const recipientIds = new Set<string>();
    const geo = await resolveGeographicContext(track);
    const geoRoles = roles.filter((r) => ROLE_TO_FIELD[r.trim()]);
    const otherRoles = roles.filter((r) => !ROLE_TO_FIELD[r.trim()]);

    if (geo && geoRoles.length > 0) {
        for (const id of userIdsForGeographicRoles(geo, geoRoles)) {
            recipientIds.add(id);
        }
        const missingGeoRoles = geoRoles.filter((role) => {
            const field = ROLE_TO_FIELD[role.trim()];
            return field && !geo[field];
        });
        for (const role of missingGeoRoles) {
            const users = await db.User.findAll({ where: { roleCode: role.trim(), status: 'active' } });
            users.forEach((u: any) => recipientIds.add(u.id));
        }
    } else if (geoRoles.length > 0) {
        for (const role of geoRoles) {
            const users = await db.User.findAll({ where: { roleCode: role.trim(), status: 'active' } });
            users.forEach((u: any) => recipientIds.add(u.id));
        }
    }

    for (const role of otherRoles) {
        const users = await db.User.findAll({ where: { roleCode: role.trim(), status: 'active' } });
        users.forEach((u: any) => recipientIds.add(u.id));
    }

    return [...recipientIds];
}
