import { CanActivate, ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import type { Request } from 'express';
import { PrismaService } from '../prisma/prisma.service';
import type { AuthenticatedUser } from '../auth/auth.types';
import { REQUIRED_PERMISSION_KEY, type PermissionAction } from './permission.decorator';

type PermissionRequirement = {
  module: string;
  action: PermissionAction;
};

type PermissionRow = {
  id?: unknown;
  values?: unknown;
};

const fallbackRolePermissions: Record<string, Record<string, Partial<Record<PermissionAction, boolean>>>> = {
  admin: {
    campaigns: { view: true, edit: true, delete: true, manage: true },
    templates: { view: true, edit: true, delete: true, manage: true },
    users: { view: true, edit: true, delete: true, manage: true },
    roles: { view: true, edit: true, delete: true, manage: true },
    contacts: { view: true, edit: true, delete: true, manage: true },
    conversations: { view: true, edit: true, delete: true, manage: true },
    analytics: { view: true, edit: true, delete: true, manage: true },
    settings: { view: true, edit: true, delete: true, manage: true },
  },
  editor: {
    campaigns: { view: true, edit: true, delete: false, manage: false },
    templates: { view: true, edit: true, delete: false, manage: false },
    contacts: { view: true, edit: false, delete: false, manage: false },
    conversations: { view: true, edit: true, delete: false, manage: false },
    analytics: { view: true, edit: false, delete: false, manage: false },
  },
  agent: {
    campaigns: { view: true, edit: false, delete: false, manage: false },
    templates: { view: true, edit: false, delete: false, manage: false },
    contacts: { view: true, edit: true, delete: false, manage: false },
    conversations: { view: true, edit: true, delete: false, manage: false },
    analytics: { view: true, edit: false, delete: false, manage: false },
  },
};

@Injectable()
export class PermissionGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly prisma: PrismaService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requirement = this.reflector.getAllAndOverride<PermissionRequirement | undefined>(
      REQUIRED_PERMISSION_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requirement) {
      return true;
    }

    const request = context.switchToHttp().getRequest<Request & { user?: AuthenticatedUser }>();
    const authUser = request.user;
    if (!authUser?.sub) {
      throw new ForbiddenException('Missing authenticated user context');
    }

    const user = await this.prisma.user.findUnique({
      where: { id: authUser.sub },
      select: {
        role: {
          select: {
            key: true,
            permissionsJson: true,
          },
        },
      },
    });

    if (!user?.role) {
      throw new ForbiddenException('No role assigned to this account');
    }

    if (user.role.key === 'admin') {
      return true;
    }

    const allowed =
      this.hasPermissionFromRoleJson(user.role.permissionsJson, requirement.module, requirement.action) ||
      this.hasFallbackPermission(user.role.key, requirement.module, requirement.action);

    if (!allowed) {
      throw new ForbiddenException(`Missing permission: ${requirement.module}.${requirement.action}`);
    }

    return true;
  }

  private hasPermissionFromRoleJson(
    permissionsJson: unknown,
    module: string,
    action: PermissionAction,
  ) {
    if (!Array.isArray(permissionsJson)) {
      return false;
    }

    const row = permissionsJson.find((item): item is PermissionRow => {
      if (!item || typeof item !== 'object') return false;
      return typeof (item as PermissionRow).id === 'string' && (item as PermissionRow).id === module;
    });

    if (!row || !row.values || typeof row.values !== 'object') {
      return false;
    }

    const value = (row.values as Record<string, unknown>)[action];
    return value === true;
  }

  private hasFallbackPermission(roleKey: string, module: string, action: PermissionAction) {
    return fallbackRolePermissions[roleKey]?.[module]?.[action] === true;
  }
}
