// src/server/features/reference_books/access.service.ts
// Business logic for external access management

import { db } from '@serverShared/db/client';
import { externalAccess, externalAccessPending } from './db/external_access.table';
import { employees } from '@serverShared/db/schema';
import { eq, or, isNull } from 'drizzle-orm';
import type { UserInfo } from '@serverShared/lib/auth';
import { AppPermission } from '@shared/contracts/permissions';
import { hasPermission, hasAnyPermission } from '@serverShared/plugins/rbac';
import type { CreateAccessRequest, UpdateAccessRequest, ExternalAccess, ExternalAccessPendingEntry } from '@shared/contracts/access';
import { mapToAccessEntry, mapToAccessPendingEntry, type ExternalAccessWithMetadata } from './lib/access.mapper';
import { createDraft, updateDraft, deleteRequest } from './lib/access.drafts';
import { executeCreate, executeUpdate, executeDelete, autoApproveDraft, updatePendingDraft, deletePendingDraft } from './lib/access.core';

export const accessService = {
  async create(data: CreateAccessRequest, user: UserInfo): Promise<ExternalAccessWithMetadata> {
    const canDirectEdit = await hasPermission(user, AppPermission.ACCESS_EDIT);
    const canRequestApproval = await hasPermission(user, AppPermission.ACCESS_REQUEST_APPROVAL);
    const canCreate = await hasAnyPermission(user, [AppPermission.ACCESS_EDIT, AppPermission.ACCESS_REQUEST_APPROVAL]);

    if (!canCreate) {
      throw new Error('У вас нет прав на создание внешних доступов');
    }

    const payload = { ...data, createdBy: user.id };

    if (canDirectEdit) {
      return executeCreate(payload);
    }

    if (!canRequestApproval) {
      throw new Error('У вас нет прав на создание заявки на согласование');
    }

    return createDraft(payload);
  },

  async findAll(status: 'all' | 'pending' | 'approved' = 'all'): Promise<ExternalAccess[]> {
    if (status === 'pending') {
      const pending = await accessService.getAccessPendingEntries();
      return pending.map((entry) => ({
        id: entry.id,
        resourceName: entry.resourceName,
        link: entry.link,
        login: entry.login,
        password: entry.password,
        comment: entry.comment,
        isApproved: false,
        createdBy: entry.createdBy,
        createdAt: entry.createdAt,
        updatedAt: entry.updatedAt,
        archivedAt: null,
      }));
    }

    const entries = await db.select()
      .from(externalAccess)
      .where(isNull(externalAccess.archivedAt))
      .orderBy(externalAccess.createdAt);

    if (status === 'approved') {
      return entries.filter((entry) => entry.isApproved).map(mapToAccessEntry);
    }

    return entries.map(mapToAccessEntry);
  },

  async findById(id: string): Promise<ExternalAccess | null> {
    const entry = await db.select()
      .from(externalAccess)
      .where(eq(externalAccess.id, id))
      .limit(1);

    if (!entry[0]) {
      return null;
    }

    return mapToAccessEntry(entry[0]);
  },

  async update(id: string, data: UpdateAccessRequest, user: UserInfo): Promise<ExternalAccessWithMetadata> {
    const canDirectEdit = await hasPermission(user, AppPermission.ACCESS_EDIT);
    const canRequestApproval = await hasPermission(user, AppPermission.ACCESS_REQUEST_APPROVAL);
    const canUpdate = await hasAnyPermission(user, [AppPermission.ACCESS_EDIT, AppPermission.ACCESS_REQUEST_APPROVAL]);

    if (!canUpdate) {
      throw new Error('У вас нет прав на изменение внешних доступов');
    }

    let existing = await db.select().from(externalAccess).where(eq(externalAccess.id, id)).limit(1);

    let isPendingRecord = false;
    let pendingRecord: typeof externalAccessPending.$inferSelect | null = null;

    if (!existing[0]) {
      const pendingExisting = await db.select().from(externalAccessPending).where(eq(externalAccessPending.id, id)).limit(1);

      if (pendingExisting[0]) {
        existing = pendingExisting as any;
        pendingRecord = pendingExisting[0];
        isPendingRecord = true;
      }
    }

    if (!existing[0]) {
      throw new Error('Запись не найдена');
    }

    if (isPendingRecord) {
      if (canDirectEdit) {
        const updates: any = { updatedAt: new Date() };
        if (data.resourceName !== undefined) updates.resourceName = data.resourceName.trim();
        if (data.link !== undefined) updates.link = data.link.trim();
        if (data.login !== undefined) updates.login = data.login.trim();
        if (data.password !== undefined) updates.password = data.password.trim();
        if (data.comment !== undefined) updates.comment = data.comment.trim() || null;

        return autoApproveDraft(id, pendingRecord!, user.id, updates);
      }

      if (!canRequestApproval) {
        throw new Error('У вас нет прав на создание заявки на согласование');
      }

      if (pendingRecord?.createdBy !== user.id) {
        throw new Error('У вас нет прав на редактирование этого черновика');
      }

      const updates: any = { updatedAt: new Date() };
      if (data.resourceName !== undefined) updates.resourceName = data.resourceName.trim();
      if (data.link !== undefined) updates.link = data.link.trim();
      if (data.login !== undefined) updates.login = data.login.trim();
      if (data.password !== undefined) updates.password = data.password.trim();
      if (data.comment !== undefined) updates.comment = data.comment.trim() || null;

      return updatePendingDraft(id, updates);
    }

    if (canDirectEdit) {
      return executeUpdate(id, data);
    }

    if (!canRequestApproval) {
      throw new Error('У вас нет прав на создание заявки на согласование');
    }

    return updateDraft({ ...data, id, updatedBy: user.id }, existing[0]);
  },

  async softDelete(id: string, user: UserInfo): Promise<void> {
    const canDirectEdit = await hasPermission(user, AppPermission.ACCESS_EDIT);
    const canRequestApproval = await hasPermission(user, AppPermission.ACCESS_REQUEST_APPROVAL);
    const canDelete = await hasAnyPermission(user, [AppPermission.ACCESS_EDIT, AppPermission.ACCESS_REQUEST_APPROVAL]);

    if (!canDelete) {
      throw new Error('У вас нет прав на удаление внешних доступов');
    }

    let existing = await db.select().from(externalAccess).where(eq(externalAccess.id, id)).limit(1);
    let isPendingRecord = false;

    if (!existing[0]) {
      const pendingExisting = await db.select().from(externalAccessPending).where(eq(externalAccessPending.id, id)).limit(1);
      if (pendingExisting[0]) {
        existing = pendingExisting as any;
        isPendingRecord = true;
      }
    }

    if (!existing[0]) {
      throw new Error('Запись не найдена');
    }

    if (isPendingRecord) {
      if (!canDirectEdit && (existing[0] as any).createdBy !== user.id) {
        throw new Error('У вас нет прав на удаление этого черновика');
      }

      return deletePendingDraft(id);
    }

    if (canDirectEdit) {
      return executeDelete(id);
    }

    if (!canRequestApproval) {
      throw new Error('У вас нет прав на создание заявки на удаление');
    }

    return deleteRequest(id, existing[0], user.id);
  },

  async getAccessPendingEntries(): Promise<ExternalAccessPendingEntry[]> {
    const entries = await db.select({
      id: externalAccessPending.id,
      resourceName: externalAccessPending.resourceName,
      link: externalAccessPending.link,
      login: externalAccessPending.login,
      password: externalAccessPending.password,
      comment: externalAccessPending.comment,
      status: externalAccessPending.status,
      targetId: externalAccessPending.targetId,
      createdBy: externalAccessPending.createdBy,
      reviewedBy: externalAccessPending.reviewedBy,
      reviewedAt: externalAccessPending.reviewedAt,
      rejectionReason: externalAccessPending.rejectionReason,
      createdAt: externalAccessPending.createdAt,
      updatedAt: externalAccessPending.updatedAt,
      creatorName: employees.fullName,
    })
      .from(externalAccessPending)
      .leftJoin(employees, eq(externalAccessPending.createdBy, employees.id))
      .where(or(
        eq(externalAccessPending.status, 'pending'),
        eq(externalAccessPending.status, 'to_delete'),
      ))
      .orderBy(externalAccessPending.createdAt);

    return entries.map(mapToAccessPendingEntry);
  },
};
