import { ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import type { CurrentUser } from './auth-context.service';

export interface ResourceScope {
  productId?: string;
  projectId?: string;
  versionId?: string;
}

interface ResolvedScope extends ResourceScope {
  versionMembers?: unknown;
}

const VIEW_ONLY = ['product:view', 'project:view', 'version:view', 'requirement:view', 'version.req:view'];

const SYSTEM_ROLE_PERMISSIONS: Record<string, string[]> = {
  'role-admin': ['*'],
  'role-pm': [
    ...std4('product'),
    ...std4('project'),
    ...std4('version'),
    ...std4('requirement'),
    'version.req:view', 'version.req:manage',
    'version.product_plan:view', 'version.product_plan:manage',
    'xiaobao.warning:view', 'xiaobao.warning:manage',
    'overtime:view', 'member:view', 'role:view',
    'version.research:view', 'version.ui_plan:view', 'version.devtask:view',
    'version.testcase:view', 'version.bug:view',
    'work-activity:manage',
  ],
  'role-dev': [
    ...VIEW_ONLY,
    'version.devtask:view', 'version.devtask:manage',
    'version.bug:view', 'version.bug:edit',
    'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.testcase:view',
    'xiaobao.warning:view',
    'overtime:view', 'overtime:create',
    'work-activity:manage',
  ],
  'role-test': [
    ...VIEW_ONLY,
    'version.testcase:view', 'version.testcase:manage',
    'version.bug:view', 'version.bug:create', 'version.bug:edit', 'version.bug:delete',
    'version.research:view', 'version.product_plan:view', 'version.ui_plan:view', 'version.devtask:view',
    'xiaobao.warning:view',
    'overtime:view', 'overtime:create',
    'work-activity:manage',
  ],
  'role-design': [
    ...VIEW_ONLY,
    'version.ui_plan:view', 'version.ui_plan:manage',
    'version.research:view', 'version.product_plan:view', 'version.devtask:view',
    'version.testcase:view', 'version.bug:view',
    'xiaobao.warning:view',
    'overtime:view', 'overtime:create',
    'work-activity:manage',
  ],
  'role-lead': [
    ...VIEW_ONLY,
    'version.research:view', 'version.product_plan:view', 'version.ui_plan:view',
    'version.devtask:view', 'version.devtask:manage',
    'version.testcase:view', 'version.testcase:manage',
    'version.bug:view', 'version.bug:create', 'version.bug:edit', 'version.bug:delete',
    'xiaobao.warning:view',
    'overtime:view', 'overtime:create', 'overtime:export',
    'work-activity:manage',
  ],
};

const PROJECT_ROLE_PERMISSIONS: Record<string, string[]> = {
  owner: ['*'],
  admin: ['*'],
  member: [
    'project:view',
    'version:view',
    'requirement:view',
    'version.req:view',
    'version.research:view', 'version.research:manage',
    'version.product_plan:view', 'version.product_plan:manage',
    'version.ui_plan:view', 'version.ui_plan:manage',
    'version.devtask:view', 'version.devtask:manage',
    'version.testcase:view', 'version.testcase:manage',
    'version.bug:view', 'version.bug:create', 'version.bug:edit',
    'overtime:view', 'overtime:create',
    'xiaobao.warning:view',
    'work-activity:manage',
  ],
  viewer: [
    'project:view',
    'version:view',
    'requirement:view',
    'version.req:view',
    'version.research:view',
    'version.product_plan:view',
    'version.ui_plan:view',
    'version.devtask:view',
    'version.testcase:view',
    'version.bug:view',
    'xiaobao.warning:view',
  ],
};

const VERSION_WORK_MUTATION_PERMISSIONS = new Set([
  'version.req:manage',
  'version.research:manage',
  'version.product_plan:manage',
  'version.ui_plan:manage',
  'version.devtask:manage',
  'version.testcase:manage',
  'version.bug:create',
  'version.bug:edit',
  'version.bug:delete',
]);

@Injectable()
export class PermissionService {
  constructor(private readonly prisma: PrismaService) {}

  async can(user: CurrentUser | null | undefined, permission: string, scope: ResourceScope = {}): Promise<boolean> {
    if (!user) return false;
    if ((SYSTEM_ROLE_PERMISSIONS[user.roleId] ?? []).includes('*')) return true;
    const systemPermissions = await this.resolveRolePermissions(user.roleId);
    if (systemPermissions.includes('*')) return true;

    const hasSystemPermission = systemPermissions.includes(permission);
    const hasScopedResource = Boolean(scope.projectId || scope.versionId);
    if (!hasScopedResource) return hasSystemPermission;

    const resolvedScope = await this.resolveScope(scope);
    const projectMember = resolvedScope.projectId ? await this.prisma.projectMember.findUnique({
      where: { projectId_userId: { projectId: resolvedScope.projectId, userId: user.id } },
    }) : null;

    if (requiresVersionWorkMembership(permission, resolvedScope)) {
      return hasSystemPermission && isVersionMember(user, resolvedScope.versionMembers);
    }

    if (projectMember && roleAllows(PROJECT_ROLE_PERMISSIONS[projectMember.role] ?? [], permission)) {
      return true;
    }

    if (hasSystemPermission && isVersionMember(user, resolvedScope.versionMembers)) return true;
    return false;
  }

  async assertCan(user: CurrentUser | null | undefined, permission: string, scope: ResourceScope = {}): Promise<void> {
    if (!user) throw new UnauthorizedException('Authentication required');
    if (!(await this.can(user, permission, scope))) {
      throw new ForbiddenException(`Missing permission: ${permission}`);
    }
  }

  private async resolveScope(scope: ResourceScope): Promise<ResolvedScope> {
    if (!scope.versionId || scope.projectId) return scope;
    const version = await this.prisma.version.findUnique({
      where: { id: scope.versionId },
      select: { id: true, projectId: true, members: true },
    });
    return {
      ...scope,
      projectId: version?.projectId ?? scope.projectId,
      versionMembers: version?.members,
    };
  }

  private async resolveRolePermissions(roleId: string): Promise<string[]> {
    const configuredPermissions = await this.findConfiguredRolePermissions(roleId);
    return configuredPermissions ?? SYSTEM_ROLE_PERMISSIONS[roleId] ?? [];
  }

  private async findConfiguredRolePermissions(roleId: string): Promise<string[] | null> {
    const findUnique = (this.prisma as unknown as {
      appData?: { findUnique?: (args: unknown) => Promise<{ value?: unknown } | null> };
    }).appData?.findUnique;
    if (!findUnique) return null;

    const row = await findUnique({
      where: { key: 'members' },
      select: { value: true },
    }).catch(() => null);

    if (!row) return null;
    return readRolePermissions(row.value, roleId);
  }
}

function std4(module: string): string[] {
  return [`${module}:view`, `${module}:create`, `${module}:edit`, `${module}:delete`];
}

function roleAllows(permissions: string[], permission: string): boolean {
  return permissions.includes('*') || permissions.includes(permission);
}

function requiresVersionWorkMembership(permission: string, scope: ResourceScope): boolean {
  return Boolean(scope.versionId && VERSION_WORK_MUTATION_PERMISSIONS.has(permission));
}

function readRolePermissions(value: unknown, roleId: string): string[] | null {
  if (!value || typeof value !== 'object') return null;
  const roles = (value as { roles?: unknown }).roles;
  if (!Array.isArray(roles)) return null;
  const role = roles.find((item) => (
    item &&
    typeof item === 'object' &&
    (item as { id?: unknown }).id === roleId
  ));
  if (!role || typeof role !== 'object') return null;
  const permissions = (role as { permissions?: unknown }).permissions;
  if (!Array.isArray(permissions)) return null;
  return permissions.filter((item): item is string => typeof item === 'string');
}

function isVersionMember(user: CurrentUser, members: unknown): boolean {
  if (!Array.isArray(members)) return false;
  return members.some((member) => {
    if (typeof member === 'string') return member === user.id || member === user.name;
    if (!member || typeof member !== 'object') return false;
    const record = member as { id?: unknown; userId?: unknown; name?: unknown };
    return record.id === user.id || record.userId === user.id || record.name === user.name;
  });
}
