import { Inject, Injectable } from '@nestjs/common';
import type { Kysely } from 'kysely';
import { KYSELY_DB } from '../../database/src';
import type { Database } from '../../database/src';
import type {
  TripMember,
  TripMemberRole,
  TripMemberStatus,
} from './trip.types';

function toTripMember(row: {
  id: string;
  trip_id: string;
  user_id: string;
  role: string;
  status: string;
  joined_at: Date | null;
  created_at: Date;
  updated_at: Date;
}): TripMember {
  return {
    id: row.id,
    tripId: row.trip_id,
    userId: row.user_id,
    role: row.role as TripMemberRole,
    status: row.status as TripMemberStatus,
    joinedAt: row.joined_at,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

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

  async findByTripAndUser(
    tripId: string,
    userId: string,
  ): Promise<TripMember | undefined> {
    const row = await this.db
      .selectFrom('trip_members')
      .selectAll()
      .where('trip_id', '=', tripId)
      .where('user_id', '=', userId)
      .executeTakeFirst();
    return row ? toTripMember(row) : undefined;
  }

  async listByTrip(tripId: string): Promise<TripMember[]> {
    const rows = await this.db
      .selectFrom('trip_members')
      .selectAll()
      .where('trip_id', '=', tripId)
      .execute();
    return rows.map(toTripMember);
  }

  async upsertActiveMember(
    tripId: string,
    userId: string,
    role: TripMemberRole = 'MEMBER',
  ): Promise<TripMember> {
    const row = await this.db
      .insertInto('trip_members')
      .values({
        trip_id: tripId,
        user_id: userId,
        role,
        status: 'ACTIVE',
        joined_at: new Date().toISOString(),
      })
      .onConflict((oc) =>
        oc.columns(['trip_id', 'user_id']).doUpdateSet({
          status: 'ACTIVE',
          joined_at: new Date().toISOString(),
          updated_at: new Date().toISOString(),
        }),
      )
      .returningAll()
      .executeTakeFirstOrThrow();

    return toTripMember(row);
  }

  async updateRoleOrStatus(
    tripId: string,
    memberId: string,
    patch: { role?: TripMemberRole; status?: TripMemberStatus },
  ): Promise<TripMember | undefined> {
    const row = await this.db
      .updateTable('trip_members')
      .set({ ...patch, updated_at: new Date().toISOString() })
      .where('trip_id', '=', tripId)
      .where('id', '=', memberId)
      .returningAll()
      .executeTakeFirst();
    return row ? toTripMember(row) : undefined;
  }

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