/**
 * Drizzle Change Set Repository Adapter
 *
 * Implements ChangeSetRepository interface using Drizzle ORM.
 */

import { eq, desc, asc, and } from 'drizzle-orm';
import { nanoid } from 'nanoid';
import type { DrizzleDatabase } from '../../index.js';
import { changeSets, changeSetEntries, type ChangeSet } from '../../schema.js';
import type {
  ChangeSetRepository,
  CreateChangeSetData,
  CreateChangeSetEntryData,
  ChangeSetWithEntries,
} from '../change-set-repository.js';

export class DrizzleChangeSetRepository implements ChangeSetRepository {
  constructor(private db: DrizzleDatabase) {}

  async createWithEntries(data: CreateChangeSetData, entries: CreateChangeSetEntryData[]): Promise<ChangeSet> {
    const id = nanoid();
    const now = new Date();

    // Use transaction for atomicity
    return this.db.transaction(async (tx) => {
      const [created] = await tx.insert(changeSets).values({
        id,
        agentId: data.agentId,
        agentName: data.agentName,
        initiativeId: data.initiativeId,
        mode: data.mode,
        summary: data.summary ?? null,
        status: 'applied',
        createdAt: now,
      }).returning();

      if (entries.length > 0) {
        const entryRows = entries.map((e, i) => ({
          id: nanoid(),
          changeSetId: id,
          entityType: e.entityType,
          entityId: e.entityId,
          action: e.action,
          previousState: e.previousState ?? null,
          newState: e.newState ?? null,
          sortOrder: e.sortOrder ?? i,
          createdAt: now,
        }));

        await tx.insert(changeSetEntries).values(entryRows);
      }

      return created;
    });
  }

  async findById(id: string): Promise<ChangeSet | null> {
    const result = await this.db
      .select()
      .from(changeSets)
      .where(eq(changeSets.id, id))
      .limit(1);

    return result[0] ?? null;
  }

  async findByIdWithEntries(id: string): Promise<ChangeSetWithEntries | null> {
    const cs = await this.findById(id);
    if (!cs) return null;

    const entries = await this.db
      .select()
      .from(changeSetEntries)
      .where(eq(changeSetEntries.changeSetId, id))
      .orderBy(asc(changeSetEntries.sortOrder));

    return { ...cs, entries };
  }

  async findByInitiativeId(initiativeId: string): Promise<ChangeSet[]> {
    return this.db
      .select()
      .from(changeSets)
      .where(eq(changeSets.initiativeId, initiativeId))
      .orderBy(desc(changeSets.createdAt));
  }

  async findByAgentId(agentId: string): Promise<ChangeSet[]> {
    return this.db
      .select()
      .from(changeSets)
      .where(eq(changeSets.agentId, agentId))
      .orderBy(desc(changeSets.createdAt));
  }

  async findAppliedByCreatedEntity(entityType: string, entityId: string): Promise<ChangeSetWithEntries[]> {
    // Find changeset entries matching the entity
    const matchingEntries = await this.db
      .select({ changeSetId: changeSetEntries.changeSetId })
      .from(changeSetEntries)
      .where(
        and(
          eq(changeSetEntries.entityType, entityType as any),
          eq(changeSetEntries.entityId, entityId),
          eq(changeSetEntries.action, 'create'),
        ),
      );

    const results: ChangeSetWithEntries[] = [];
    const seen = new Set<string>();
    for (const { changeSetId } of matchingEntries) {
      if (seen.has(changeSetId)) continue;
      seen.add(changeSetId);
      const cs = await this.findByIdWithEntries(changeSetId);
      if (cs && cs.status === 'applied') {
        results.push(cs);
      }
    }
    return results;
  }

  async markReverted(id: string): Promise<ChangeSet> {
    const [updated] = await this.db
      .update(changeSets)
      .set({ status: 'reverted', revertedAt: new Date() })
      .where(eq(changeSets.id, id))
      .returning();

    if (!updated) {
      throw new Error(`ChangeSet not found: ${id}`);
    }

    return updated;
  }
}
