import type { DB } from "@/internal/datastore/db";
import type { Kysely } from "kysely";

export class CampaignGroupRepository {
    private db: Kysely<DB>;

    constructor(db: Kysely<DB>) {
        this.db = db;
    }

    async create(data: { name: string; description?: string | null; created_by?: string | null }) {
        return await this.db
            .insertInto("campaign_groups")
            .values({
                name: data.name,
                description: data.description ?? null,
                created_by: data.created_by ?? null,
            })
            .returningAll()
            .executeTakeFirstOrThrow();
    }

    /**
     * Creates the parent, its new subcampaigns and attaches existing campaigns in one transaction,
     * so a failure part-way leaves nothing behind.
     */
    async createWithCampaigns(
        group: { name: string; description?: string | null; created_by?: string | null },
        campaigns: {
            name: string;
            title: string;
            body: string;
            image_url: string | null;
            target_criteria: unknown;
            additional_data: unknown;
            status: string;
            scheduled_at: Date | null;
            total_count: number;
        }[],
        attachIds: string[],
    ) {
        return await this.db.transaction().execute(async (trx) => {
            const created = await trx
                .insertInto("campaign_groups")
                .values({ name: group.name, description: group.description ?? null, created_by: group.created_by ?? null })
                .returningAll()
                .executeTakeFirstOrThrow();

            const inserted = campaigns.length
                ? await trx
                    .insertInto("campaigns")
                    .values(campaigns.map((c) => ({
                        ...c,
                        target_criteria: JSON.stringify(c.target_criteria ?? {}),
                        additional_data: JSON.stringify(c.additional_data ?? {}),
                        published_by: group.created_by ?? null,
                        parent_id: created.id,
                    })))
                    .returning(["id"])
                    .execute()
                : [];

            if (attachIds.length) {
                await trx
                    .updateTable("campaigns")
                    .set({ parent_id: created.id, updated_at: new Date() })
                    .where("id", "in", attachIds)
                    .where("status", "!=", "DELETED")
                    .execute();
            }

            return { group: created, campaignIds: inserted.map((r) => r.id) };
        });
    }

    async update(id: string, data: { name?: string; description?: string | null; status?: string }) {
        const set: Record<string, unknown> = { updated_at: new Date() };
        if (data.name !== undefined) set.name = data.name;
        if (data.description !== undefined) set.description = data.description;
        if (data.status !== undefined) set.status = data.status;

        return await this.db
            .updateTable("campaign_groups")
            .set(set)
            .where("id", "=", id)
            .returningAll()
            .executeTakeFirst();
    }

    async getById(id: string) {
        return await this.db
            .selectFrom("campaign_groups")
            .selectAll()
            .where("id", "=", id)
            .where("status", "!=", "DELETED")
            .executeTakeFirst();
    }

    async list(filters: { search?: string } = {}) {
        let query = this.db
            .selectFrom("campaign_groups")
            .selectAll()
            .where("status", "!=", "DELETED")
            .orderBy("created_at", "desc");

        if (filters.search) {
            const pattern = `%${filters.search}%`;
            query = query.where((eb) => eb.or([
                eb("name", "ilike", pattern),
                eb("description", "ilike", pattern),
                eb.exists(
                    eb.selectFrom("campaign_group_tags")
                        .innerJoin("campaign_tags", "campaign_tags.id", "campaign_group_tags.tag_id")
                        .select("campaign_group_tags.group_id")
                        .whereRef("campaign_group_tags.group_id", "=", "campaign_groups.id")
                        .where("campaign_tags.name", "ilike", pattern),
                ),
            ]));
        }

        return await query.execute();
    }

    /** All non-deleted subcampaigns for the given groups, ordered for timeline display. */
    async getChildren(groupIds: string[]) {
        if (groupIds.length === 0) return [];
        return await this.db
            .selectFrom("campaigns")
            .selectAll()
            .where("parent_id", "in", groupIds)
            .where("status", "!=", "DELETED")
            .orderBy("scheduled_at", "asc")
            .orderBy("created_at", "asc")
            .execute();
    }

    async attach(groupId: string, campaignIds: string[]) {
        if (campaignIds.length === 0) return [];
        return await this.db
            .updateTable("campaigns")
            .set({ parent_id: groupId, updated_at: new Date() })
            .where("id", "in", campaignIds)
            .where("status", "!=", "DELETED")
            .returning(["id"])
            .execute();
    }

    async detach(groupId: string, campaignIds?: string[]) {
        let query = this.db
            .updateTable("campaigns")
            .set({ parent_id: null, paused_by_parent: false, updated_at: new Date() })
            .where("parent_id", "=", groupId);
        if (campaignIds) {
            if (campaignIds.length === 0) return [];
            query = query.where("id", "in", campaignIds);
        }
        return await query.returning(["id"]).execute();
    }

    /** Stop every pending/active subcampaign and flag it so a parent resume restarts only these. */
    async pauseChildren(groupId: string) {
        return await this.db
            .updateTable("campaigns")
            .set({ status: "STOPPED", paused_by_parent: true, updated_at: new Date() })
            .where("parent_id", "=", groupId)
            .where("status", "in", ["SCHEDULED", "QUEUED", "SENDING"])
            .returning(["id"])
            .execute();
    }

    async getParentPausedChildren(groupId: string) {
        return await this.db
            .selectFrom("campaigns")
            .selectAll()
            .where("parent_id", "=", groupId)
            .where("paused_by_parent", "=", true)
            .where("status", "=", "STOPPED")
            .execute();
    }

    async clearParentPausedFlag(groupId: string) {
        return await this.db
            .updateTable("campaigns")
            .set({ paused_by_parent: false })
            .where("parent_id", "=", groupId)
            .where("paused_by_parent", "=", true)
            .execute();
    }
}
