import type { PermissionOverrideValue, ProjectUser, UserRole } from "@prisma/client";
import { prisma } from "./prisma";

export type PermissionKey =
  | "project.view"
  | "project.create"
  | "project.edit"
  | "project.archive"
  | "project.delete"
  | "users.view"
  | "users.create"
  | "users.edit"
  | "users.disable"
  | "users.resetPassword"
  | "users.managePermissions"
  | "partners.view"
  | "partners.create"
  | "partners.edit"
  | "partners.archive"
  | "partners.delete"
  | "partners.editOwnership"
  | "categories.view"
  | "categories.create"
  | "categories.edit"
  | "categories.archive"
  | "categories.delete"
  | "contributions.view"
  | "contributions.create"
  | "contributions.edit"
  | "contributions.archive"
  | "contributions.delete"
  | "purchaseCosts.view"
  | "purchaseCosts.create"
  | "purchaseCosts.edit"
  | "purchaseCosts.archive"
  | "purchaseCosts.delete"
  | "renovationCosts.view"
  | "renovationCosts.create"
  | "renovationCosts.edit"
  | "renovationCosts.archive"
  | "renovationCosts.delete"
  | "operatingExpenses.view"
  | "operatingExpenses.create"
  | "operatingExpenses.edit"
  | "operatingExpenses.archive"
  | "operatingExpenses.delete"
  | "expenses.view"
  | "expenses.create"
  | "expenses.edit"
  | "expenses.archive"
  | "expenses.delete"
  | "expenses.export"
  | "recurringBills.view"
  | "recurringBills.create"
  | "recurringBills.edit"
  | "recurringBills.archive"
  | "recurringBills.delete"
  | "recurringBills.markPaid"
  | "receipts.view"
  | "receipts.upload"
  | "receipts.replace"
  | "receipts.archive"
  | "receipts.delete"
  | "receipts.download"
  | "documents.view"
  | "documents.upload"
  | "documents.edit"
  | "documents.archive"
  | "documents.delete"
  | "documents.download"
  | "tenants.view"
  | "tenants.create"
  | "tenants.edit"
  | "tenants.archive"
  | "tenants.delete"
  | "units.view"
  | "units.create"
  | "units.edit"
  | "units.archive"
  | "units.delete"
  | "rentPayments.view"
  | "rentPayments.create"
  | "rentPayments.edit"
  | "rentPayments.archive"
  | "rentPayments.delete"
  | "securityDeposits.view"
  | "securityDeposits.create"
  | "securityDeposits.edit"
  | "securityDeposits.archive"
  | "securityDeposits.delete"
  | "profitLoss.view"
  | "profitLoss.export"
  | "settlement.view"
  | "settlement.export"
  | "dashboard.view"
  | "reports.view"
  | "reports.export"
  | "settings.view"
  | "settings.edit"
  | "settings.manageApp"
  | "approvals.view"
  | "approvals.approve"
  | "approvals.reject"
  | "audit.view";

export type PermissionMap = Record<PermissionKey, boolean>;

export const roleLabels: Record<UserRole, string> = {
  OWNER: "Owner",
  ADMIN: "Admin",
  EDITOR: "Editor",
  CONTRIBUTOR: "Contributor",
  VIEWER: "Viewer",
  RECEIPT_UPLOADER: "Receipt Uploader",
  TENANT: "Tenant",
  CUSTOM: "Custom"
};

export const userStatusLabels = {
  ACTIVE: "Active",
  INVITED: "Invited",
  DISABLED: "Disabled"
} as const;

export const projectUserStatusLabels = {
  ACTIVE: "Active",
  INVITED: "Invited",
  DISABLED: "Disabled"
} as const;

export const overrideLabels: Record<PermissionOverrideValue, string> = {
  DEFAULT: "Default from role",
  ALLOW: "Overridden: Allowed",
  DENY: "Overridden: Denied"
};

export const permissionGroups: Array<{ label: string; permissions: Array<{ key: PermissionKey; label: string }> }> = [
  {
    label: "Project",
    permissions: [
      { key: "project.view", label: "View project" },
      { key: "project.create", label: "Create project" },
      { key: "project.edit", label: "Edit project" },
      { key: "project.archive", label: "Archive project" },
      { key: "project.delete", label: "Delete project" }
    ]
  },
  {
    label: "Users",
    permissions: [
      { key: "users.view", label: "View users" },
      { key: "users.create", label: "Create users" },
      { key: "users.edit", label: "Edit users" },
      { key: "users.disable", label: "Disable users" },
      { key: "users.resetPassword", label: "Reset passwords" },
      { key: "users.managePermissions", label: "Manage roles and permissions" }
    ]
  },
  {
    label: "Partners",
    permissions: [
      { key: "partners.view", label: "View partners" },
      { key: "partners.create", label: "Create partners" },
      { key: "partners.edit", label: "Edit partners" },
      { key: "partners.archive", label: "Archive partners" },
      { key: "partners.delete", label: "Delete partners" },
      { key: "partners.editOwnership", label: "Edit ownership" }
    ]
  },
  {
    label: "Categories",
    permissions: [
      { key: "categories.view", label: "View categories" },
      { key: "categories.create", label: "Create categories" },
      { key: "categories.edit", label: "Edit categories" },
      { key: "categories.archive", label: "Archive categories" },
      { key: "categories.delete", label: "Delete categories" }
    ]
  },
  {
    label: "Costs and Expenses",
    permissions: [
      { key: "contributions.view", label: "View contributions" },
      { key: "contributions.create", label: "Create contributions" },
      { key: "contributions.edit", label: "Edit contributions" },
      { key: "contributions.archive", label: "Archive contributions" },
      { key: "contributions.delete", label: "Delete contributions" },
      { key: "purchaseCosts.view", label: "View purchase costs" },
      { key: "purchaseCosts.create", label: "Create purchase costs" },
      { key: "purchaseCosts.edit", label: "Edit purchase costs" },
      { key: "purchaseCosts.archive", label: "Archive purchase costs" },
      { key: "purchaseCosts.delete", label: "Delete purchase costs" },
      { key: "renovationCosts.view", label: "View renovation costs" },
      { key: "renovationCosts.create", label: "Create renovation costs" },
      { key: "renovationCosts.edit", label: "Edit renovation costs" },
      { key: "renovationCosts.archive", label: "Archive renovation costs" },
      { key: "renovationCosts.delete", label: "Delete renovation costs" },
      { key: "operatingExpenses.view", label: "View operating expenses" },
      { key: "operatingExpenses.create", label: "Create operating expenses" },
      { key: "operatingExpenses.edit", label: "Edit operating expenses" },
      { key: "operatingExpenses.archive", label: "Archive operating expenses" },
      { key: "operatingExpenses.delete", label: "Delete operating expenses" },
      { key: "expenses.view", label: "View all expenses" },
      { key: "expenses.create", label: "Create all expenses" },
      { key: "expenses.edit", label: "Edit all expenses" },
      { key: "expenses.archive", label: "Archive all expenses" },
      { key: "expenses.delete", label: "Delete all expenses" },
      { key: "expenses.export", label: "Export expenses" }
    ]
  },
  {
    label: "Recurring Bills",
    permissions: [
      { key: "recurringBills.view", label: "View recurring bills" },
      { key: "recurringBills.create", label: "Create recurring bills" },
      { key: "recurringBills.edit", label: "Edit recurring bills" },
      { key: "recurringBills.archive", label: "Archive recurring bills" },
      { key: "recurringBills.delete", label: "Delete recurring bills" },
      { key: "recurringBills.markPaid", label: "Mark recurring bills paid" }
    ]
  },
  {
    label: "Receipts and Documents",
    permissions: [
      { key: "receipts.view", label: "View receipts" },
      { key: "receipts.upload", label: "Upload receipts" },
      { key: "receipts.replace", label: "Replace receipts" },
      { key: "receipts.archive", label: "Archive receipts" },
      { key: "receipts.delete", label: "Delete receipts" },
      { key: "receipts.download", label: "Download receipts" },
      { key: "documents.view", label: "View documents" },
      { key: "documents.upload", label: "Upload documents" },
      { key: "documents.edit", label: "Edit documents" },
      { key: "documents.archive", label: "Archive documents" },
      { key: "documents.delete", label: "Delete documents" },
      { key: "documents.download", label: "Download documents" }
    ]
  },
  {
    label: "Tenants and Rent",
    permissions: [
      { key: "tenants.view", label: "View tenants" },
      { key: "tenants.create", label: "Create tenants" },
      { key: "tenants.edit", label: "Edit tenants" },
      { key: "tenants.archive", label: "Archive tenants" },
      { key: "tenants.delete", label: "Delete tenants" },
      { key: "units.view", label: "View units" },
      { key: "units.create", label: "Create units" },
      { key: "units.edit", label: "Edit units" },
      { key: "units.archive", label: "Archive units" },
      { key: "units.delete", label: "Delete units" },
      { key: "rentPayments.view", label: "View rent payments" },
      { key: "rentPayments.create", label: "Create rent payments" },
      { key: "rentPayments.edit", label: "Edit rent payments" },
      { key: "rentPayments.archive", label: "Archive rent payments" },
      { key: "rentPayments.delete", label: "Delete rent payments" },
      { key: "securityDeposits.view", label: "View security deposits" },
      { key: "securityDeposits.create", label: "Create security deposits" },
      { key: "securityDeposits.edit", label: "Edit security deposits" },
      { key: "securityDeposits.archive", label: "Archive security deposits" },
      { key: "securityDeposits.delete", label: "Delete security deposits" }
    ]
  },
  {
    label: "Reports and Settings",
    permissions: [
      { key: "profitLoss.view", label: "View profit/loss" },
      { key: "profitLoss.export", label: "Export profit/loss" },
      { key: "settlement.view", label: "View settlements" },
      { key: "settlement.export", label: "Export settlements" },
      { key: "dashboard.view", label: "View dashboard" },
      { key: "reports.view", label: "View reports" },
      { key: "reports.export", label: "Export reports" },
      { key: "settings.view", label: "View settings" },
      { key: "settings.edit", label: "Edit settings" },
      { key: "settings.manageApp", label: "Manage app settings" },
      { key: "approvals.view", label: "View approvals" },
      { key: "approvals.approve", label: "Approve submissions" },
      { key: "approvals.reject", label: "Reject submissions" },
      { key: "audit.view", label: "View audit log" }
    ]
  }
];

export const allPermissionKeys = permissionGroups.flatMap((group) => group.permissions.map((permission) => permission.key));

function permissionMap(keys: PermissionKey[] = []): PermissionMap {
  return Object.fromEntries(allPermissionKeys.map((key) => [key, keys.includes(key)])) as PermissionMap;
}

const viewKeys = allPermissionKeys.filter((key) => key.endsWith(".view") || key.endsWith(".download")) as PermissionKey[];
const editKeys = allPermissionKeys.filter((key) => !key.endsWith(".delete") && !key.startsWith("users.") && key !== "settings.manageApp" && key !== "audit.view") as PermissionKey[];
const createUploadKeys = allPermissionKeys.filter(
  (key) => key.endsWith(".view") || key.endsWith(".create") || key.endsWith(".upload") || key.endsWith(".download") || key === "receipts.replace"
) as PermissionKey[];

export const rolePermissionDefaults: Record<UserRole, PermissionMap> = {
  OWNER: permissionMap(allPermissionKeys),
  ADMIN: permissionMap(allPermissionKeys.filter((key) => key !== "project.delete")),
  EDITOR: permissionMap([
    ...editKeys,
    "expenses.export",
    "reports.view",
    "documents.download",
    "receipts.download",
    "recurringBills.markPaid"
  ]),
  CONTRIBUTOR: permissionMap([
    ...createUploadKeys,
    "dashboard.view",
    "project.view",
    "expenses.view",
    "purchaseCosts.view",
    "renovationCosts.view",
    "operatingExpenses.view",
    "documents.view",
    "receipts.view"
  ]),
  VIEWER: permissionMap([
    ...viewKeys,
    "dashboard.view",
    "project.view",
    "reports.view",
    "profitLoss.view",
    "settlement.view"
  ]),
  RECEIPT_UPLOADER: permissionMap([
    "dashboard.view",
    "project.view",
    "expenses.view",
    "purchaseCosts.view",
    "renovationCosts.view",
    "operatingExpenses.view",
    "documents.view",
    "documents.upload",
    "documents.download",
    "receipts.view",
    "receipts.upload",
    "receipts.replace",
    "receipts.download"
  ]),
  TENANT: permissionMap([
    "project.view",
    "tenants.view",
    "units.view",
    "rentPayments.view",
    "documents.view",
    "documents.download",
    "receipts.view",
    "receipts.download"
  ]),
  CUSTOM: permissionMap([])
};

export class PermissionError extends Error {
  constructor(public permissionKey: PermissionKey, public projectId: string) {
    super(`You do not have permission to ${permissionKey} on this project.`);
  }
}

export async function seedRolePermissions() {
  for (const [role, permissions] of Object.entries(rolePermissionDefaults) as Array<[UserRole, PermissionMap]>) {
    for (const key of allPermissionKeys) {
      await prisma.rolePermission.upsert({
        where: { role_permissionKey: { role, permissionKey: key } },
        update: { allowed: permissions[key] },
        create: { role, permissionKey: key, allowed: permissions[key] }
      });
    }
  }
}

export async function ensureOwnerProjectAccess(projectId: string, ownerId: string) {
  return prisma.projectUser.upsert({
    where: { projectId_userId: { projectId, userId: ownerId } },
    update: { role: "OWNER", status: "ACTIVE" },
    create: { projectId, userId: ownerId, role: "OWNER", status: "ACTIVE" }
  });
}

export async function ensureUserOwnedProjectAccess(userId: string) {
  const ownedProjects = await prisma.project.findMany({
    where: { ownerId: userId },
    select: { id: true }
  });
  await Promise.all(ownedProjects.map((project) => ensureOwnerProjectAccess(project.id, userId)));
}

export async function getUserProjectMembership(userId: string, projectId: string) {
  const user = await prisma.user.findUnique({ where: { id: userId }, select: { status: true } });
  if (!user || user.status === "DISABLED") return null;

  const project = await prisma.project.findUnique({
    where: { id: projectId },
    select: { id: true, ownerId: true }
  });
  if (!project) return null;
  if (project.ownerId === userId) await ensureOwnerProjectAccess(project.id, userId);

  return prisma.projectUser.findFirst({
    where: { projectId, userId, status: "ACTIVE" },
    include: { overrides: true }
  });
}

export async function getUserProjectRole(userId: string, projectId: string) {
  const membership = await getUserProjectMembership(userId, projectId);
  return membership?.role ?? null;
}

export async function getRolePermissions(role: UserRole): Promise<PermissionMap> {
  const rows = await prisma.rolePermission.findMany({ where: { role } });
  if (rows.length === 0) {
    await seedRolePermissions();
    return rolePermissionDefaults[role];
  }

  const permissions = { ...rolePermissionDefaults[role] };
  for (const row of rows) {
    if (allPermissionKeys.includes(row.permissionKey as PermissionKey)) {
      permissions[row.permissionKey as PermissionKey] = row.allowed;
    }
  }
  return permissions;
}

export async function getUserProjectOverrides(userId: string, projectId: string) {
  const membership = await getUserProjectMembership(userId, projectId);
  if (!membership) return new Map<PermissionKey, PermissionOverrideValue>();
  return new Map(
    membership.overrides
      .filter((override) => allPermissionKeys.includes(override.permissionKey as PermissionKey))
      .map((override) => [override.permissionKey as PermissionKey, override.value])
  );
}

export async function getResolvedProjectPermissions(userId: string, projectId: string): Promise<{
  membership: (ProjectUser & { overrides: Array<{ permissionKey: string; value: PermissionOverrideValue }> }) | null;
  permissions: PermissionMap;
}> {
  const membership = await getUserProjectMembership(userId, projectId);
  if (!membership) return { membership: null, permissions: permissionMap([]) };

  const permissions = await getRolePermissions(membership.role);
  for (const override of membership.overrides) {
    if (!allPermissionKeys.includes(override.permissionKey as PermissionKey)) continue;
    if (override.value === "DENY") permissions[override.permissionKey as PermissionKey] = false;
    if (override.value === "ALLOW") permissions[override.permissionKey as PermissionKey] = true;
  }

  return { membership, permissions };
}

export async function hasPermission(userId: string, projectId: string, permissionKey: PermissionKey) {
  const { permissions } = await getResolvedProjectPermissions(userId, projectId);
  return permissions[permissionKey] === true;
}

export async function requirePermission(userId: string, projectId: string, permissionKey: PermissionKey) {
  const allowed = await hasPermission(userId, projectId, permissionKey);
  if (!allowed) throw new PermissionError(permissionKey, projectId);
}

export async function writeAuditLog(input: {
  userId?: string | null;
  projectId?: string | null;
  action: string;
  entityType: string;
  entityId?: string | null;
  oldValue?: unknown;
  newValue?: unknown;
}) {
  try {
    await prisma.auditLog.create({
      data: {
        userId: input.userId ?? null,
        projectId: input.projectId ?? null,
        action: input.action,
        entityType: input.entityType,
        entityId: input.entityId ?? null,
        oldValue: input.oldValue === undefined ? undefined : JSON.parse(JSON.stringify(input.oldValue)),
        newValue: input.newValue === undefined ? undefined : JSON.parse(JSON.stringify(input.newValue))
      }
    });
  } catch {
    // Audit failures should not block the user's primary action.
  }
}

export function permissionForCostPhase(returnTo?: string | null): {
  view: PermissionKey;
  create: PermissionKey;
  edit: PermissionKey;
  archive: PermissionKey;
  delete: PermissionKey;
} {
  if (returnTo?.includes("renovation-costs")) {
    return {
      view: "renovationCosts.view",
      create: "renovationCosts.create",
      edit: "renovationCosts.edit",
      archive: "renovationCosts.archive",
      delete: "renovationCosts.delete"
    };
  }
  if (returnTo?.includes("operating-expenses")) {
    return {
      view: "operatingExpenses.view",
      create: "operatingExpenses.create",
      edit: "operatingExpenses.edit",
      archive: "operatingExpenses.archive",
      delete: "operatingExpenses.delete"
    };
  }
  if (returnTo?.includes("purchase-costs")) {
    return {
      view: "purchaseCosts.view",
      create: "purchaseCosts.create",
      edit: "purchaseCosts.edit",
      archive: "purchaseCosts.archive",
      delete: "purchaseCosts.delete"
    };
  }
  return {
    view: "expenses.view",
    create: "expenses.create",
    edit: "expenses.edit",
    archive: "expenses.archive",
    delete: "expenses.delete"
  };
}
