import { Inject, Injectable } from '@nestjs/common';
import { sql } from 'kysely';
import type { Kysely } from 'kysely';
import { KYSELY_DB } from '../../database/src';
import type { Database } from '../../database/src';
import { DEFAULT_TRIP_SETTINGS } from './trip.types';
import type { CreateTripDto, Trip, TripStatus } from './trip.types';

function toTrip(row: {
  id: string;
  name: string;
  description: string | null;
  owner_id: string;
  start_date: string | null;
  end_date: string | null;
  status: string;
  planning_stage: string | null;
  currency: string;
  version: number;
  created_at: Date;
  updated_at: Date;
}): Trip {
  return {
    id: row.id,
    name: row.name,
    description: row.description,
    ownerId: row.owner_id,
    startDate: row.start_date,
    endDate: row.end_date,
    status: row.status as TripStatus,
    planningStage: row.planning_stage,
    currency: row.currency,
    version: row.version,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

export interface TripUpdateFields {
  name: string;
  description: string | null;
  start_date: string | null;
  end_date: string | null;
  status: string;
  planning_stage: string | null;
  currency: string;
}

@Injectable()
export class TripsRepository {
  constructor(@Inject(KYSELY_DB) private readonly db: Kysely<Database>) {}

  async createTrip(ownerId: string, dto: CreateTripDto): Promise<Trip> {
    return this.db.transaction().execute(async (trx) => {
      const trip = await trx
        .insertInto('trips')
        .values({
          name: dto.name,
          description: dto.description ?? null,
          owner_id: ownerId,
          start_date: dto.startDate ?? null,
          end_date: dto.endDate ?? null,
          status: 'DRAFT',
          currency: dto.currency ?? 'EUR',
        })
        .returningAll()
        .executeTakeFirstOrThrow();

      await trx
        .insertInto('trip_settings')
        .values({
          trip_id: trip.id,
          web_research_enabled: DEFAULT_TRIP_SETTINGS.webResearchEnabled,
          periodic_agent_review_enabled:
            DEFAULT_TRIP_SETTINGS.periodicAgentReviewEnabled,
          notification_email_enabled:
            DEFAULT_TRIP_SETTINGS.notificationEmailEnabled,
          notification_push_enabled:
            DEFAULT_TRIP_SETTINGS.notificationPushEnabled,
        })
        .execute();

      await trx
        .insertInto('trip_members')
        .values({
          trip_id: trip.id,
          user_id: ownerId,
          role: 'OWNER',
          status: 'ACTIVE',
          joined_at: new Date().toISOString(),
        })
        .execute();

      return toTrip(trip);
    });
  }

  async findById(tripId: string): Promise<Trip | undefined> {
    const row = await this.db
      .selectFrom('trips')
      .selectAll()
      .where('id', '=', tripId)
      .executeTakeFirst();
    return row ? toTrip(row) : undefined;
  }

  async listForUser(userId: string): Promise<Trip[]> {
    const rows = await this.db
      .selectFrom('trips')
      .innerJoin('trip_members', 'trip_members.trip_id', 'trips.id')
      .selectAll('trips')
      .where('trip_members.user_id', '=', userId)
      .where('trip_members.status', '=', 'ACTIVE')
      .execute();
    return rows.map(toTrip);
  }

  async updateWithVersionCheck(
    tripId: string,
    expectedVersion: number,
    patch: Partial<TripUpdateFields>,
  ): Promise<Trip | undefined> {
    const row = await this.db
      .updateTable('trips')
      .set({
        ...patch,
        version: sql`version + 1`,
        updated_at: new Date().toISOString(),
      })
      .where('id', '=', tripId)
      .where('version', '=', expectedVersion)
      .returningAll()
      .executeTakeFirst();

    return row ? toTrip(row) : undefined;
  }

  async delete(tripId: string): Promise<void> {
    await this.db.deleteFrom('trips').where('id', '=', tripId).execute();
  }
}
