import { FactionMembershipModel } from "../models/FactionMembershipModel.js";
import { ConnectionStore } from "./ConnectionStore.js";
import { ConnectionTargetResolver } from "./ConnectionTargetResolver.js";

export class FactionMembershipService {
    static canSetMembership(leftTarget, rightTarget) {
        const endpoints = this.#resolveTargetEndpoints(leftTarget, rightTarget);
        return !!endpoints && endpoints.factionNode.id !== endpoints.memberNode.id;
    }

    static isMembershipEdge(edge = {}) {
        return FactionMembershipModel.isMembership(edge);
    }

    static isLeaderEdge(edge = {}) {
        return FactionMembershipModel.isLeader(edge);
    }

    static getMembership(edgeOrId) {
        const edge = typeof edgeOrId === "string" ? ConnectionStore.getConnectionEdge(edgeOrId) : edgeOrId;
        if (!FactionMembershipModel.isMembership(edge)) return null;
        const membership = edge.factionMembership;
        const factionNode = ConnectionStore.getNode(membership.factionNodeId);
        const memberNode = ConnectionStore.getNode(membership.memberNodeId);
        if (!factionNode || !memberNode) return null;
        return {
            edgeId: edge.id,
            edge,
            factionNode,
            memberNode,
            factionNodeId: factionNode.id,
            memberNodeId: memberNode.id,
            tier: membership.tier,
            rank: foundry.utils.deepClone(membership.rank || null),
            rankValue: Number(membership.rank?.value || 0),
            rankLabel: String(membership.rank?.label || ""),
            isLeader: membership.tier === FactionMembershipModel.LEADER_TIER
        };
    }

    static getMembershipsForFaction(factionTarget, { user = game.user, includeHidden = false } = {}) {
        const factionNodeId = ConnectionTargetResolver.getNodeId(factionTarget);
        return this.#getMembershipEntries(factionNodeId)
            .filter(entry => entry.membership.factionNodeId === factionNodeId)
            .filter(entry => includeHidden || ConnectionStore.canUserSeeConnection(entry.edge, user))
            .map(entry => this.#toFactionMembershipView(entry))
            .filter(Boolean)
            .sort((a, b) => Number(b.isLeader) - Number(a.isLeader)
                || Number(b.rankValue || 0) - Number(a.rankValue || 0)
                || a.member.name.localeCompare(b.member.name));
    }

    static getMembershipsForNpc(npcTarget, { user = game.user, includeHidden = false } = {}) {
        const memberNodeId = ConnectionTargetResolver.getNodeId(npcTarget);
        return this.getMembershipRecordsForNpcNode(memberNodeId)
            .filter(entry => includeHidden || ConnectionStore.canUserSeeConnection(entry.edge, user))
            .map(entry => this.#toNpcMembershipView(entry))
            .filter(Boolean)
            .sort((a, b) => Number(b.rankValue || 0) - Number(a.rankValue || 0)
                || a.faction.name.localeCompare(b.faction.name));
    }

    static getMembershipRecordsForNpcNode(memberNodeId = "") {
        return this.#getMembershipEntries(memberNodeId)
            .filter(entry => entry.membership.memberNodeId === memberNodeId)
            .map(entry => ({
                edge: entry.edge,
                factionNode: entry.otherNode,
                factionNodeId: entry.membership.factionNodeId,
                memberNodeId: entry.membership.memberNodeId,
                tier: entry.membership.tier,
                rank: foundry.utils.deepClone(entry.membership.rank || null),
                rankValue: Number(entry.membership.rank?.value || 0),
                rankLabel: String(entry.membership.rank?.label || ""),
                isLeader: entry.membership.tier === FactionMembershipModel.LEADER_TIER
            }));
    }

    static getLeader(factionTarget, options = {}) {
        return this.getMembershipsForFaction(factionTarget, options).find(membership => membership.isLeader) || null;
    }

    static async setMember(factionTarget, memberTarget, options = {}) {
        return this.#setTier(factionTarget, memberTarget, FactionMembershipModel.MEMBER_TIER, options);
    }

    static async setLeader(factionTarget, memberTarget, options = {}) {
        return this.#setTier(factionTarget, memberTarget, FactionMembershipModel.LEADER_TIER, options);
    }

    static async clearMembership(edgeOrId) {
        if (!game.user?.isGM) return false;
        const edge = typeof edgeOrId === "string" ? ConnectionStore.getConnectionEdge(edgeOrId) : edgeOrId;
        if (!FactionMembershipModel.isMembership(edge)) return false;
        await ConnectionStore.updateConnection(edge.id, { factionMembership: null });
        return true;
    }

    static async setRank(edgeOrId, rank = null) {
        if (!game.user?.isGM) return null;
        const edge = typeof edgeOrId === "string" ? ConnectionStore.getConnectionEdge(edgeOrId) : edgeOrId;
        if (!FactionMembershipModel.isMembership(edge)) return null;
        const normalizedRank = FactionMembershipModel.normalizeRank(rank);
        return ConnectionStore.runBatch(async () => {
            await ConnectionStore.updateConnection(edge.id, {
                factionMembership: {
                    ...edge.factionMembership,
                    ...(normalizedRank ? { rank: normalizedRank } : { rank: null })
                }
            });
            const displayRole = normalizedRank?.label || (edge.factionMembership.tier === FactionMembershipModel.LEADER_TIER
                ? "Faction Leader"
                : "Member");
            for (const nodeId of [edge.factionMembership.factionNodeId, edge.factionMembership.memberNodeId]) {
                const view = ConnectionStore.getConnectionView(edge, nodeId);
                await ConnectionStore.setConnectionViewForNode(edge.id, nodeId, {
                    ...view,
                    role: displayRole
                });
            }
            return ConnectionStore.getConnectionEdge(edge.id);
        }, { reason: "setFactionMembershipRank" });
    }

    static async #setTier(factionTarget, memberTarget, tier, {
        edgeId = "",
        role = "",
        sourceRole = "",
        targetRole = "",
        rank
    } = {}) {
        if (!game.user?.isGM) return null;
        const endpoints = this.#resolveTargetEndpoints(factionTarget, memberTarget);
        if (!endpoints) throw new Error("Faction membership requires one Organization and one Person.");
        const { factionNode, memberNode } = endpoints;
        return ConnectionStore.runBatch(async () => {
            if (tier === FactionMembershipModel.LEADER_TIER) {
                for (const current of this.#getMembershipEntries(factionNode.id)) {
                    if (current.membership.factionNodeId !== factionNode.id) continue;
                    if (current.edge.id === edgeId || current.membership.memberNodeId === memberNode.id) continue;
                    if (current.membership.tier !== FactionMembershipModel.LEADER_TIER) continue;
                    await ConnectionStore.updateConnection(current.edge.id, {
                        factionMembership: {
                            ...current.membership,
                            tier: FactionMembershipModel.MEMBER_TIER
                        }
                    });
                }
            }

            const selected = edgeId
                ? ConnectionStore.getConnectionEdge(edgeId)
                : this.#findEdge(factionNode.id, memberNode.id);
            const membership = FactionMembershipModel.create(factionNode, memberNode, tier, {
                rank: rank === undefined ? selected?.factionMembership?.rank : rank
            });
            if (selected) {
                if (!this.#connects(selected, factionNode.id, memberNode.id)) {
                    throw new Error("The selected Connection does not join this Organization and Person.");
                }
                return ConnectionStore.updateConnection(selected.id, { factionMembership: membership });
            }

            const defaultRole = tier === FactionMembershipModel.LEADER_TIER ? "Faction Leader" : "Member";
            const displayRole = String(role || defaultRole).trim() || defaultRole;
            return ConnectionStore.addConnection(factionNode, memberNode, {
                factionMembership: membership,
                category: "member",
                role: String(sourceRole || displayRole).trim() || displayRole,
                sourceView: {
                    category: "member",
                    role: String(sourceRole || displayRole).trim() || displayRole
                },
                targetView: {
                    category: "affiliation",
                    role: String(targetRole || displayRole).trim() || displayRole
                }
            });
        }, { reason: tier === FactionMembershipModel.LEADER_TIER ? "setFactionLeader" : "setFactionMember" });
    }

    static #getMembershipEntries(nodeId = "") {
        if (!nodeId) return [];
        return (ConnectionStore.getGraphContext().adjacency.get(nodeId) || [])
            .map(({ edge, node }) => ({
                edge,
                otherNode: node,
                membership: edge.factionMembership
            }))
            .filter(entry => !!entry.membership);
    }

    static #toFactionMembershipView(entry) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(entry.otherNode) || entry.otherNode;
        if (!display) return null;
        return {
            edgeId: entry.edge.id,
            memberNodeId: entry.membership.memberNodeId,
            tier: entry.membership.tier,
            rank: foundry.utils.deepClone(entry.membership.rank || null),
            rankValue: Number(entry.membership.rank?.value || 0),
            rankLabel: String(entry.membership.rank?.label || ""),
            isLeader: entry.membership.tier === FactionMembershipModel.LEADER_TIER,
            member: {
                id: display.id,
                entityId: display.entityId || "",
                name: display.name || "Unknown Member",
                imageSrc: display.img || "",
                subtitle: display.subtitle || ""
            }
        };
    }

    static #toNpcMembershipView(entry) {
        const display = ConnectionTargetResolver.resolveDisplayNodeSync(entry.factionNode) || entry.factionNode;
        if (!display) return null;
        return {
            edgeId: entry.edge.id,
            factionNodeId: entry.factionNodeId,
            tier: entry.tier,
            rank: foundry.utils.deepClone(entry.rank || null),
            rankValue: Number(entry.rankValue || 0),
            rankLabel: String(entry.rankLabel || ""),
            isLeader: entry.isLeader,
            faction: {
                id: display.id,
                entityId: display.entityId || "",
                name: display.name || "Unknown Organization",
                imageSrc: display.img || "",
                subtitle: display.subtitle || ""
            }
        };
    }

    static #resolveTargetEndpoints(leftTarget, rightTarget) {
        const leftNode = this.#nodeLike(leftTarget);
        const rightNode = this.#nodeLike(rightTarget);
        return FactionMembershipModel.resolveEndpoints(leftNode, rightNode);
    }

    static #nodeLike(target) {
        const id = ConnectionTargetResolver.getNodeId(target);
        if (!id) return null;
        return ConnectionStore.getNode(id) || { ...target, id };
    }

    static #findEdge(leftNodeId, rightNodeId) {
        return (ConnectionStore.getGraphContext().adjacency.get(leftNodeId) || [])
            .find(({ node }) => node.id === rightNodeId)?.edge || null;
    }

    static #connects(edge, leftNodeId, rightNodeId) {
        return (edge?.sourceNodeId === leftNodeId && edge?.targetNodeId === rightNodeId)
            || (edge?.sourceNodeId === rightNodeId && edge?.targetNodeId === leftNodeId);
    }
}
