import { eq, desc, count, and, isNull, sql } from 'drizzle-orm';
import { db } from '@/db';
import { liveScopes, images } from '@banatie/database';
import type { LiveScope, NewLiveScope, LiveScopeFilters, LiveScopeWithStats } from '@/types/models';
import { buildWhereClause, buildEqCondition } from '@/utils/helpers';
import { ERROR_MESSAGES } from '@/utils/constants';

export class LiveScopeService {
  /**
   * Create new live scope
   * @param data - New scope data (projectId, slug, settings)
   * @returns Created scope record
   */
  async create(data: NewLiveScope): Promise<LiveScope> {
    const [scope] = await db.insert(liveScopes).values(data).returning();
    if (!scope) {
      throw new Error('Failed to create live scope record');
    }
    return scope;
  }

  /**
   * Get scope by ID
   * @param id - Scope UUID
   * @returns Scope record or null
   */
  async getById(id: string): Promise<LiveScope | null> {
    const scope = await db.query.liveScopes.findFirst({
      where: eq(liveScopes.id, id),
    });
    return scope || null;
  }

  /**
   * Get scope by slug within a project
   * @param projectId - Project UUID
   * @param slug - Scope slug
   * @returns Scope record or null
   */
  async getBySlug(projectId: string, slug: string): Promise<LiveScope | null> {
    const scope = await db.query.liveScopes.findFirst({
      where: and(eq(liveScopes.projectId, projectId), eq(liveScopes.slug, slug)),
    });
    return scope || null;
  }

  /**
   * Get scope by ID or throw error
   * @param id - Scope UUID
   * @returns Scope record
   * @throws Error if not found
   */
  async getByIdOrThrow(id: string): Promise<LiveScope> {
    const scope = await this.getById(id);
    if (!scope) {
      throw new Error('Live scope not found');
    }
    return scope;
  }

  /**
   * Get scope by slug or throw error
   * @param projectId - Project UUID
   * @param slug - Scope slug
   * @returns Scope record
   * @throws Error if not found
   */
  async getBySlugOrThrow(projectId: string, slug: string): Promise<LiveScope> {
    const scope = await this.getBySlug(projectId, slug);
    if (!scope) {
      throw new Error('Live scope not found');
    }
    return scope;
  }

  /**
   * Get scope with computed statistics
   * @param id - Scope UUID
   * @returns Scope with currentGenerations count and lastGeneratedAt
   */
  async getByIdWithStats(id: string): Promise<LiveScopeWithStats> {
    const scope = await this.getByIdOrThrow(id);

    // Count images in this scope (use meta field: { scope: slug, isLiveUrl: true })
    const scopeImages = await db.query.images.findMany({
      where: and(
        eq(images.projectId, scope.projectId),
        isNull(images.deletedAt),
        sql`${images.meta}->>'scope' = ${scope.slug}`,
        sql`(${images.meta}->>'isLiveUrl')::boolean = true`,
      ),
      orderBy: [desc(images.createdAt)],
    });

    const currentGenerations = scopeImages.length;
    const lastGeneratedAt = scopeImages.length > 0 ? scopeImages[0]!.createdAt : null;

    return {
      ...scope,
      currentGenerations,
      lastGeneratedAt,
      images: scopeImages,
    };
  }

  /**
   * Get scope by slug with computed statistics
   * @param projectId - Project UUID
   * @param slug - Scope slug
   * @returns Scope with statistics
   */
  async getBySlugWithStats(projectId: string, slug: string): Promise<LiveScopeWithStats> {
    const scope = await this.getBySlugOrThrow(projectId, slug);
    return this.getByIdWithStats(scope.id);
  }

  /**
   * List scopes in a project with pagination
   * @param filters - Query filters (projectId, optional slug)
   * @param limit - Max results to return
   * @param offset - Number of results to skip
   * @returns Array of scopes with stats and total count
   */
  async list(
    filters: LiveScopeFilters,
    limit: number,
    offset: number,
  ): Promise<{ scopes: LiveScopeWithStats[]; total: number }> {
    const conditions = [
      buildEqCondition(liveScopes, 'projectId', filters.projectId),
      buildEqCondition(liveScopes, 'slug', filters.slug),
    ];

    const whereClause = buildWhereClause(conditions);

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

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

    // Compute stats for each scope
    const scopesWithStats = await Promise.all(
      scopesList.map(async (scope) => {
        const scopeImages = await db.query.images.findMany({
          where: and(
            eq(images.projectId, scope.projectId),
            isNull(images.deletedAt),
            sql`${images.meta}->>'scope' = ${scope.slug}`,
            sql`(${images.meta}->>'isLiveUrl')::boolean = true`,
          ),
          orderBy: [desc(images.createdAt)],
        });

        return {
          ...scope,
          currentGenerations: scopeImages.length,
          lastGeneratedAt: scopeImages.length > 0 ? scopeImages[0]!.createdAt : null,
        };
      }),
    );

    return {
      scopes: scopesWithStats,
      total: Number(totalCount),
    };
  }

  /**
   * Update scope settings
   * @param id - Scope UUID
   * @param updates - Fields to update (allowNewGenerations, newGenerationsLimit, meta)
   * @returns Updated scope record
   */
  async update(
    id: string,
    updates: {
      allowNewGenerations?: boolean;
      newGenerationsLimit?: number;
      meta?: Record<string, unknown>;
    },
  ): Promise<LiveScope> {
    // Verify scope exists
    await this.getByIdOrThrow(id);

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

    if (!updated) {
      throw new Error('Failed to update live scope');
    }

    return updated;
  }

  /**
   * Delete scope (hard delete)
   * Note: Images in this scope are preserved with meta.scope field
   * @param id - Scope UUID
   */
  async delete(id: string): Promise<void> {
    await db.delete(liveScopes).where(eq(liveScopes.id, id));
  }

  /**
   * Check if scope can accept new generations
   * @param scope - Scope record
   * @param currentCount - Current number of generations (optional, will query if not provided)
   * @returns true if new generations are allowed
   */
  async canGenerateNew(scope: LiveScope, currentCount?: number): Promise<boolean> {
    if (!scope.allowNewGenerations) {
      return false;
    }

    if (currentCount === undefined) {
      const stats = await this.getByIdWithStats(scope.id);
      currentCount = stats.currentGenerations;
    }

    return currentCount < scope.newGenerationsLimit;
  }

  /**
   * Create scope automatically (lazy creation) with project defaults
   * @param projectId - Project UUID
   * @param slug - Scope slug
   * @param projectDefaults - Default settings from project (allowNewGenerations, limit)
   * @returns Created scope or existing scope if already exists
   */
  async createOrGet(
    projectId: string,
    slug: string,
    projectDefaults: {
      allowNewLiveScopes: boolean;
      newLiveScopesGenerationLimit: number;
    },
  ): Promise<LiveScope> {
    // Check if scope already exists
    const existing = await this.getBySlug(projectId, slug);
    if (existing) {
      return existing;
    }

    // Check if project allows new scope creation
    if (!projectDefaults.allowNewLiveScopes) {
      throw new Error(ERROR_MESSAGES.SCOPE_CREATION_DISABLED);
    }

    // Create new scope with project defaults
    return this.create({
      projectId,
      slug,
      allowNewGenerations: true,
      newGenerationsLimit: projectDefaults.newLiveScopesGenerationLimit,
      meta: {},
    });
  }
}
