import { Inject, Injectable, Logger } from '@nestjs/common';
import {
  ApprovalState,
  Prisma,
  Ticket,
  TicketStateHistory,
  TicketValidationState,
  Tour,
} from '@prisma/client';
import { ExpressionBuilder, Kysely } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { KYSELY_CLIENT_PROVIDER_TOKEN } from 'src/core/database/database.module';
import { PrismaService } from 'src/core/database/prisma.service';
import { DB } from 'src/core/database/types';
import { createPrismaQueryFromTicketFilters } from './ticket-filter';
import { TicketFilterDto, UpdateDocumentInfoDto } from './tickets.dto';

function withTours(eb: ExpressionBuilder<DB, 'Ticket'>) {
  return jsonArrayFrom(
    eb
      .selectFrom('Tour')
      .selectAll('Tour')
      .whereRef('Tour.ticketId', '=', 'Ticket.id')
      .orderBy('Tour.operationId'),
  ).as('tours');
}

@Injectable()
export class TicketRepository {
  private logger = new Logger(TicketRepository.name);

  constructor(
    private readonly prisma: PrismaService,
    @Inject(KYSELY_CLIENT_PROVIDER_TOKEN) private readonly db: Kysely<DB>,
  ) {}

  async findById(ticketId: string): Promise<Ticket> {
    return await this.prisma.ticket.findUnique({
      where: { id: ticketId },
      include: { tours: true },
    });
  }

  async findAllCreated(): Promise<(Ticket & { tours: Tour[] })[]> {
    return await this.prisma.ticket.findMany({
      where: {
        state: {
          none: {
            state: TicketValidationState.USER_INTERACTION_REQUIRED,
          },
        },
      },
      include: {
        tours: true,
      },
    });
  }

  async findAllClosed(): Promise<Ticket[]> {
    return await this.prisma.ticket.findMany({
      where: {
        tours: {
          every: {
            check: {
              not: 0,
            },
          },
        },
      },
    });
  }

  async findAll(): Promise<(Ticket & { tours: Tour[] })[]> {
    return await this.prisma.ticket.findMany({
      where: {
        state: {
          none: {
            state: TicketValidationState.ARCHIVED,
          },
        },
      },
      include: {
        tours: true,
      },
    });
  }

  async findByState(
    state: TicketValidationState,
    filter: TicketFilterDto,
    take?: number,
    skip?: number,
  ) {
    const query: Prisma.TicketFindManyArgs = {
      ...{
        ...(skip ? { skip, take: 10 } : {}),
        ...(take ? { take } : {}),
        ...(take && !skip ? { skip: 0 } : {}),
      },
      where: {
        AND: createPrismaQueryFromTicketFilters(filter),
        currentState: state,
        tours: {
          some: {
            check: 0,
          },
        },
      },
      orderBy: { createdAt: 'desc' },
    };

    const [tickets, count] = await this.prisma.$transaction([
      this.prisma.ticket.findMany({ ...query }),
      this.prisma.ticket.count({ where: query.where }),
    ]);
    return {
      tickets,
      total: count,
    };
  }

  async findStateHistoryByTicketId(
    ticketId: string,
  ): Promise<{ state: TicketValidationState }[]> {
    return await this.prisma.ticketStateHistory.findMany({
      where: {
        ticketId,
      },
      select: {
        state: true,
      },
    });
  }

  async findIdByTourId(tourId: string): Promise<{ id: string }> {
    return await this.prisma.ticket.findFirst({
      select: {
        id: true,
      },
      where: {
        tours: {
          some: {
            id: tourId,
          },
        },
      },
    });
  }

  async search(query: string): Promise<(Ticket & { tours: Tour[] })[]> {
    return this.prisma.ticket.findMany({
      where: {
        state: {
          none: {
            state: TicketValidationState.ARCHIVED,
          },
        },
        tours: {
          some: {
            OR: [
              ...query.split(' ').flatMap((word) => [
                {
                  patientName: {
                    contains: word,
                    mode: Prisma.QueryMode.insensitive,
                  },
                },
                {
                  patientSurname: {
                    contains: word,
                    mode: Prisma.QueryMode.insensitive,
                  },
                },
                {
                  operationId: {
                    contains: word,
                    mode: Prisma.QueryMode.insensitive,
                  },
                },
              ]),
            ],
          },
        },
      },
      take: 5,
      include: {
        tours: true,
      },
    });
  }

  async createTicket(tours: Tour[]): Promise<Ticket> {
    this.logger.log(`Creating Ticket for ${tours.length} tours`);
    try {
      return await this.db.transaction().execute(async (trx) => {
        const ticket = await trx
          .insertInto('Ticket')
          .values({
            currentState: TicketValidationState.CREATED,
          })
          .returningAll()
          .executeTakeFirst();

        await trx
          .insertInto('TicketStateHistory')
          .values({
            state: TicketValidationState.CREATED,
            ticketId: ticket.id,
          })
          .executeTakeFirst();

        await trx
          .updateTable('Tour')
          .set({
            ticketId: ticket.id,
          })
          .where(
            'id',
            'in',
            tours.map(({ id }) => id),
          )
          .execute();

        return ticket as Ticket;
      });
    } catch (error) {
      throw error;
    }
  }

  async addToursToTicket(ticketId: string, tours: Tour[]): Promise<Ticket> {
    await this.db
      .updateTable('Tour')
      .set({
        ticketId: ticketId,
      })
      .where(
        'id',
        'in',
        tours.map(({ id }) => id),
      )
      .execute();

    return (await this.db
      .selectFrom('Ticket')
      .where('id', '=', ticketId)
      .selectAll()
      .executeTakeFirst()) as Ticket;
  }

  async setActionablesOnTicket(
    ticketId: string,
    errors: string[],
    notes: string[],
  ): Promise<Ticket> {
    return (await this.db
      .updateTable('Ticket')
      .set({ errors, notes })
      .where('id', '=', ticketId)
      .returningAll()
      .executeTakeFirst()) as Ticket;
  }

  async setStateOfTicket(
    ticketId: string,
    state: TicketValidationState,
  ): Promise<TicketStateHistory> {
    await this.db
      .updateTable('Ticket')
      .set({
        currentState: state,
      })
      .where('Ticket.id', '=', ticketId)
      .execute();

    return await this.db
      .insertInto('TicketStateHistory')
      .values({ state, ticketId })
      .returningAll()
      .executeTakeFirst();
  }

  async updateTicket(
    ticketId: string,
    data: {
      state?: TicketValidationState;
      errors?: string[];
      notes?: string[];
      isUrgent?: boolean;
      urgency?: Date;
      approvalState?: ApprovalState;
      documentInfo?: UpdateDocumentInfoDto;
      approvalPath?: string;
    },
  ): Promise<Ticket & { tours: Tour[] }> {
    const { state, ...ticketData } = data;

    await this.db
      .updateTable('Ticket')
      .set({ ...ticketData })
      .where('id', '=', ticketId)
      .execute();

    if (state) {
      await this.setStateOfTicket(ticketId, state);
    }

    return (await this.db
      .selectFrom('Ticket')
      .where('id', '=', ticketId)
      .selectAll('Ticket')
      .select((eb) => withTours(eb))
      .executeTakeFirst()) as Ticket & { tours: Tour[] };
  }
}
