import { eq, desc, count } from 'drizzle-orm';
import { db } from '@/db';
import { flows, generations, images } from '@banatie/database';
import type { Flow, NewFlow, FlowFilters, FlowWithCounts } from '@/types/models';
import { buildWhereClause, buildEqCondition } from '@/utils/helpers';
import { ERROR_MESSAGES } from '@/utils/constants';
import { GenerationService } from './GenerationService';
import { ImageService } from './ImageService';

export class FlowService {
  async create(data: NewFlow): Promise<FlowWithCounts> {
    const [flow] = await db.insert(flows).values(data).returning();
    if (!flow) {
      throw new Error('Failed to create flow record');
    }

    return {
      ...flow,
      generationCount: 0,
      imageCount: 0,
    };
  }

  async getById(id: string): Promise<Flow | null> {
    const flow = await db.query.flows.findFirst({
      where: eq(flows.id, id),
    });

    return flow || null;
  }

  async getByIdOrThrow(id: string): Promise<Flow> {
    const flow = await this.getById(id);
    if (!flow) {
      throw new Error(ERROR_MESSAGES.FLOW_NOT_FOUND);
    }
    return flow;
  }

  async getByIdWithCounts(id: string): Promise<FlowWithCounts> {
    const flow = await this.getByIdOrThrow(id);

    const [genCountResult, imgCountResult] = await Promise.all([
      db
        .select({ count: count() })
        .from(generations)
        .where(eq(generations.flowId, id)),
      db
        .select({ count: count() })
        .from(images)
        .where(eq(images.flowId, id)),
    ]);

    const generationCount = Number(genCountResult[0]?.count || 0);
    const imageCount = Number(imgCountResult[0]?.count || 0);

    return {
      ...flow,
      generationCount,
      imageCount,
    };
  }

  async list(
    filters: FlowFilters,
    limit: number,
    offset: number
  ): Promise<{ flows: FlowWithCounts[]; total: number }> {
    const conditions = [
      buildEqCondition(flows, 'projectId', filters.projectId),
    ];

    const whereClause = buildWhereClause(conditions);

    const [flowsList, countResult] = await Promise.all([
      db.query.flows.findMany({
        where: whereClause,
        orderBy: [desc(flows.updatedAt)],
        limit,
        offset,
      }),
      db
        .select({ count: count() })
        .from(flows)
        .where(whereClause),
    ]);

    const totalCount = countResult[0]?.count || 0;

    const flowsWithCounts = await Promise.all(
      flowsList.map(async (flow) => {
        const [genCountResult, imgCountResult] = await Promise.all([
          db
            .select({ count: count() })
            .from(generations)
            .where(eq(generations.flowId, flow.id)),
          db
            .select({ count: count() })
            .from(images)
            .where(eq(images.flowId, flow.id)),
        ]);

        return {
          ...flow,
          generationCount: Number(genCountResult[0]?.count || 0),
          imageCount: Number(imgCountResult[0]?.count || 0),
        };
      })
    );

    return {
      flows: flowsWithCounts,
      total: Number(totalCount),
    };
  }

  async updateAliases(
    id: string,
    aliasUpdates: Record<string, string>
  ): Promise<FlowWithCounts> {
    const flow = await this.getByIdOrThrow(id);

    const currentAliases = (flow.aliases as Record<string, string>) || {};
    const updatedAliases = { ...currentAliases, ...aliasUpdates };

    const [updated] = await db
      .update(flows)
      .set({
        aliases: updatedAliases,
        updatedAt: new Date(),
      })
      .where(eq(flows.id, id))
      .returning();

    if (!updated) {
      throw new Error(ERROR_MESSAGES.FLOW_NOT_FOUND);
    }

    return await this.getByIdWithCounts(id);
  }

  async removeAlias(id: string, alias: string): Promise<FlowWithCounts> {
    const flow = await this.getByIdOrThrow(id);

    const currentAliases = (flow.aliases as Record<string, string>) || {};
    const { [alias]: removed, ...remainingAliases } = currentAliases;

    if (removed === undefined) {
      throw new Error(`Alias '${alias}' not found in flow`);
    }

    const [updated] = await db
      .update(flows)
      .set({
        aliases: remainingAliases,
        updatedAt: new Date(),
      })
      .where(eq(flows.id, id))
      .returning();

    if (!updated) {
      throw new Error(ERROR_MESSAGES.FLOW_NOT_FOUND);
    }

    return await this.getByIdWithCounts(id);
  }

  /**
   * Cascade delete for flow with alias protection (Section 7.3)
   * Operations:
   * 1. Delete all generations associated with this flowId (follows conditional delete logic)
   * 2. Delete all images associated with this flowId EXCEPT images with project alias
   * 3. For images with alias: keep image, set flowId=NULL
   * 4. Delete flow record from DB
   */
  async delete(id: string): Promise<void> {
    // Get all generations in this flow
    const flowGenerations = await db.query.generations.findMany({
      where: eq(generations.flowId, id),
    });

    // Delete each generation (follows conditional delete logic from Section 7.2)
    const generationService = new GenerationService();
    for (const gen of flowGenerations) {
      await generationService.delete(gen.id);
    }

    // Get all images in this flow
    const flowImages = await db.query.images.findMany({
      where: eq(images.flowId, id),
    });

    const imageService = new ImageService();
    for (const img of flowImages) {
      if (img.alias) {
        // Image has project alias → keep, unlink from flow
        await db
          .update(images)
          .set({ flowId: null, updatedAt: new Date() })
          .where(eq(images.id, img.id));
      } else {
        // Image without alias → delete
        await imageService.hardDelete(img.id);
      }
    }

    // Delete flow record
    await db.delete(flows).where(eq(flows.id, id));
  }

  async getFlowGenerations(
    flowId: string,
    limit: number,
    offset: number
  ): Promise<{ generations: any[]; total: number }> {
    const whereClause = eq(generations.flowId, flowId);

    const [generationsList, countResult] = await Promise.all([
      db.query.generations.findMany({
        where: whereClause,
        orderBy: [desc(generations.createdAt)],
        limit,
        offset,
        with: {
          outputImage: true,
        },
      }),
      db
        .select({ count: count() })
        .from(generations)
        .where(whereClause),
    ]);

    const totalCount = countResult[0]?.count || 0;

    return {
      generations: generationsList,
      total: Number(totalCount),
    };
  }

  async getFlowImages(
    flowId: string,
    limit: number,
    offset: number
  ): Promise<{ images: any[]; total: number }> {
    const whereClause = eq(images.flowId, flowId);

    const [imagesList, countResult] = await Promise.all([
      db.query.images.findMany({
        where: whereClause,
        orderBy: [desc(images.createdAt)],
        limit,
        offset,
      }),
      db
        .select({ count: count() })
        .from(images)
        .where(whereClause),
    ]);

    const totalCount = countResult[0]?.count || 0;

    return {
      images: imagesList,
      total: Number(totalCount),
    };
  }
}
