import {
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { PermissionsRepository } from './permissions.repository';
import { CreatePermissionDto } from './dto/create-permission.dto';
import { UpdatePermissionDto } from './dto/update-permission.dto';
import { ListPermissionsDto } from './dto/list-permissions.dto';

// Default auth guard a permission applies to when none is provided.
const DEFAULT_GUARD = 'web';

const PERMISSION_MATRIX_ACTIONS = [
  { key: 'read', label: 'View' },
  { key: 'create', label: 'Create' },
  { key: 'update', label: 'Update' },
  { key: 'delete', label: 'Delete' },
  { key: 'export', label: 'Export' },
] as const;

const PERMISSION_MATRIX_RESOURCES = [
  { resource: 'User', label: 'Users', order: 1 },
  { resource: 'Role', label: 'Roles', order: 2 },
  { resource: 'Permission', label: 'Permissions', order: 3 },
  { resource: 'Stakeholder', label: 'Stakeholders', order: 4 },
  {
    resource: 'WorkingGroupIssue',
    label: 'Working Group Issues',
    order: 5,
  },
  { resource: 'IssueMatrix', label: 'Issue Matrix', order: 6 },
  { resource: 'Meeting', label: 'Meetings', order: 7 },
  {
    resource: 'MeetingRequest',
    label: 'Meeting Requests',
    order: 8,
  },
  {
    resource: 'MeetingSummary',
    label: 'Meeting Summaries',
    order: 9,
  },
  {
    resource: 'CdcIssueMatrix',
    label: 'CDC Issue Matrix',
    order: 10,
  },
  {
    resource: 'CefpIssueMatrix',
    label: 'CEFP Issue Matrix',
    order: 11,
  },
  {
    resource: 'ProgressReport',
    label: 'Progress Reports',
    order: 12,
  },
  {
    resource: 'ProgressReportMinistry',
    label: 'Ministry Progress Reports',
    order: 13,
  },
] as const;

type PermissionMatrixActionKey =
  (typeof PERMISSION_MATRIX_ACTIONS)[number]['key'];

type PermissionMatrixItem = {
  id: number;
  name: string;
  label: string;
};

type PermissionMatrixResource = {
  resource: string;
  label: string;
  order: number;
  permissions: Partial<Record<PermissionMatrixActionKey, PermissionMatrixItem>>;
};

@Injectable()
export class PermissionsService {
  constructor(private readonly permissions: PermissionsRepository) {}

  // GET many permissions
  list(query: ListPermissionsDto) {
    return this.permissions.findMany(query);
  }

  // GET permissions grouped for the role create/edit checkbox matrix.
  async getPermissionMatrix(): Promise<{
    resources: PermissionMatrixResource[];
  }> {
    const permissions = await this.permissions.findMany({});
    const permissionsByName = new Map(
      permissions.map((permission) => [permission.name, permission]),
    );

    const permissionMatrixResources = PERMISSION_MATRIX_RESOURCES.map(
      (matrixResource) => {
        const resourcePermissions: PermissionMatrixResource['permissions'] = {};

        for (const matrixAction of PERMISSION_MATRIX_ACTIONS) {
          const permissionName = `${matrixAction.key} ${matrixResource.resource}`;
          const permission = permissionsByName.get(permissionName);

          if (permission) {
            resourcePermissions[matrixAction.key] = {
              id: permission.id,
              name: permission.name,
              label: matrixAction.label,
            };
          }
        }

        return {
          ...matrixResource,
          permissions: resourcePermissions,
        };
      },
    );

    return { resources: permissionMatrixResources };
  }

  // GET a single permission by its ID.
  async getOne(id: number) {
    const permission = await this.permissions.findById(id);
    if (!permission) throw new NotFoundException('Permission not found');
    return permission;
  }

  // CREATE a new permission.
  async create(dto: CreatePermissionDto) {
    const name = dto.name.trim();
    const guardName = dto.guardName?.trim() || DEFAULT_GUARD;

    await this.ensureNameAvailable(name, guardName);

    const permission = await this.permissions.create({ name, guardName });
    return { message: 'Permission created successfully.', permission };
  }

  // UPDATE an existing permission.
  async update(id: number, dto: UpdatePermissionDto) {
    const current = await this.getOne(id);

    const name = dto.name?.trim() ?? current.name;
    const guardName = dto.guardName?.trim() ?? current.guardName;

    // Only re-check uniqueness if the identity (name + guard) actually changed.
    if (name !== current.name || guardName !== current.guardName) {
      await this.ensureNameAvailable(name, guardName, id);
    }

    const permission = await this.permissions.update(id, {
      ...(dto.name !== undefined && { name }),
      ...(dto.guardName !== undefined && { guardName }),
    });
    return { message: 'Permission updated successfully.', permission };
  }

  // "Soft delete" a permission.
  async remove(id: number) {
    await this.getOne(id);
    await this.permissions.softDelete(id);
    return { message: 'Permission deleted successfully.' };
  }

  // Guard against duplicate name+guard pairs (no DB unique constraint).
  private async ensureNameAvailable(
    name: string,
    guardName: string,
    ignoreId?: number,
  ) {
    const existing = await this.permissions.findByName(name, guardName);
    if (existing && existing.id !== ignoreId) {
      throw new ConflictException(
        `Permission "${name}" already exists for guard "${guardName}"`,
      );
    }
  }
}
