import { hash } from 'bcryptjs';
import { asc, eq } from 'drizzle-orm';
import { NextRequest, NextResponse } from 'next/server';
import { memberships, organizations, users } from '@/db/schema';
import { db } from '@/lib/db';
import { isBusinessRole } from '@/lib/auth/rbac';
import { AuthError } from '@/lib/auth/session';
import {
  buildMembershipValues,
  parseUserMutationPayload,
  requireUserManagementAccess,
  validateManagedOrganizations
} from './_lib';

type UserRow = {
  userId: string;
  name: string;
  email: string;
  systemRole: string;
  activeOrganizationId: string | null;
  organizationId: string;
  organizationName: string;
  membershipRole: string;
  businessRole: string;
};

function formatUsers(rows: UserRow[]) {
  const usersMap = new Map<
    string,
    {
      id: string;
      name: string;
      email: string;
      systemRole: 'super_admin' | 'user';
      activeMembershipRole: 'admin' | 'user' | null;
      activeOrganizationId: string | null;
      organizations: Array<{ id: string; name: string; role: 'admin' | 'user' }>;
      memberships: Array<{
        organizationId: string;
        organizationName: string;
        membershipRole: 'admin' | 'user';
        businessRole: string;
      }>;
    }
  >();

  for (const row of rows) {
    const existing = usersMap.get(row.userId);
    const organization = {
      id: row.organizationId,
      name: row.organizationName,
      role: row.membershipRole === 'admin' ? 'admin' : 'user'
    } as const;
    const membership = {
      organizationId: row.organizationId,
      organizationName: row.organizationName,
      membershipRole: organization.role,
      businessRole: isBusinessRole(row.businessRole) ? row.businessRole : 'sales_support'
    } as const;

    if (existing) {
      if (!existing.organizations.some((item) => item.id === organization.id)) {
        existing.organizations.push(organization);
      }

      if (!existing.memberships.some((item) => item.organizationId === membership.organizationId)) {
        existing.memberships.push(membership);
      }

      if (row.organizationId === existing.activeOrganizationId) {
        existing.activeMembershipRole = organization.role;
      }

      continue;
    }

    usersMap.set(row.userId, {
      id: row.userId,
      name: row.name,
      email: row.email,
      systemRole: row.systemRole === 'super_admin' ? 'super_admin' : 'user',
      activeMembershipRole:
        row.organizationId === row.activeOrganizationId ? organization.role : null,
      activeOrganizationId: row.activeOrganizationId,
      organizations: [organization],
      memberships: [membership]
    });
  }

  return [...usersMap.values()].map((user) => ({
    ...user,
    organizations: user.organizations.toSorted((a, b) => a.name.localeCompare(b.name)),
    memberships: user.memberships.toSorted((a, b) =>
      a.organizationName.localeCompare(b.organizationName)
    )
  }));
}

function applyRoleFilter(usersList: ReturnType<typeof formatUsers>, roles?: string) {
  if (!roles) {
    return usersList;
  }

  const selectedRoles = roles
    .split(/[.,]/)
    .map((value) => value.trim())
    .filter(Boolean);

  return usersList.filter(
    (user) =>
      selectedRoles.includes(user.systemRole) ||
      user.memberships.some((membership) => selectedRoles.includes(membership.membershipRole))
  );
}

function sortUsers(usersList: ReturnType<typeof formatUsers>, sort?: string) {
  const defaultSort = usersList.toSorted((a, b) => a.name.localeCompare(b.name));

  if (!sort) {
    return defaultSort;
  }

  try {
    const sortItems = JSON.parse(sort) as Array<{ id: string; desc: boolean }>;
    const [primarySort] = sortItems;

    if (!primarySort) {
      return defaultSort;
    }

    const sorted = usersList.toSorted((a, b) => {
      const left =
        primarySort.id === 'role'
          ? `${a.systemRole}:${a.activeMembershipRole ?? ''}`
          : primarySort.id === 'organizations'
            ? a.memberships.map((membership) => membership.organizationName).join(', ')
            : (a[primarySort.id as 'name' | 'email'] ?? a.name);
      const right =
        primarySort.id === 'role'
          ? `${b.systemRole}:${b.activeMembershipRole ?? ''}`
          : primarySort.id === 'organizations'
            ? b.memberships.map((membership) => membership.organizationName).join(', ')
            : (b[primarySort.id as 'name' | 'email'] ?? b.name);

      return String(left).localeCompare(String(right));
    });

    return primarySort.desc ? sorted.toReversed() : sorted;
  } catch {
    return defaultSort;
  }
}

export async function GET(request: NextRequest) {
  try {
    const { isSuperAdmin, manageableOrganizationIds } = await requireUserManagementAccess();
    const { searchParams } = request.nextUrl;
    const page = Number(searchParams.get('page') ?? 1);
    const limit = Number(searchParams.get('limit') ?? 10);
    const roles = searchParams.get('roles') ?? undefined;
    const search = searchParams.get('search') ?? undefined;
    const sort = searchParams.get('sort') ?? undefined;

    const rows = await db
      .select({
        userId: users.id,
        name: users.name,
        email: users.email,
        systemRole: users.systemRole,
        activeOrganizationId: users.activeOrganizationId,
        organizationId: memberships.organizationId,
        organizationName: organizations.name,
        membershipRole: memberships.role,
        businessRole: memberships.businessRole
      })
      .from(users)
      .innerJoin(memberships, eq(memberships.userId, users.id))
      .innerJoin(organizations, eq(organizations.id, memberships.organizationId))
      .orderBy(asc(users.name));

    const scopedRows = isSuperAdmin
      ? rows
      : rows.filter((row) => manageableOrganizationIds.includes(row.organizationId));
    const searchTerm = search?.trim().toLowerCase();
    const searchedRows = searchTerm
      ? scopedRows.filter(
          (row) =>
            row.name.toLowerCase().includes(searchTerm) ||
            row.email.toLowerCase().includes(searchTerm)
        )
      : scopedRows;
    const formattedUsers = formatUsers(searchedRows);
    const filteredUsers = applyRoleFilter(formattedUsers, roles);
    const sortedUsers = sortUsers(filteredUsers, sort);
    const offset = (page - 1) * limit;
    const pagedUsers = sortedUsers.slice(offset, offset + limit);

    return NextResponse.json({
      success: true,
      time: new Date().toISOString(),
      message: 'Users loaded successfully',
      total_users: sortedUsers.length,
      offset,
      limit,
      users: pagedUsers
    });
  } catch (error) {
    if (error instanceof AuthError) {
      return NextResponse.json({ message: error.message }, { status: error.status });
    }

    return NextResponse.json({ message: 'Unable to load users' }, { status: 500 });
  }
}

export async function POST(request: NextRequest) {
  try {
    const { isSuperAdmin, manageableOrganizationIds } = await requireUserManagementAccess();
    const payload = parseUserMutationPayload(await request.json());

    if (!payload.password || payload.password.length < 8) {
      return NextResponse.json(
        { message: 'Password must be at least 8 characters' },
        { status: 400 }
      );
    }

    if (payload.systemRole === 'super_admin') {
      return NextResponse.json(
        { message: 'Creating new super admin users is not supported in this form' },
        { status: 400 }
      );
    }

    await validateManagedOrganizations(
      payload.memberships,
      manageableOrganizationIds,
      isSuperAdmin
    );

    const existingUser = await db.query.users.findFirst({
      where: eq(users.email, payload.email)
    });

    if (existingUser) {
      return NextResponse.json(
        { message: 'An account with this email already exists' },
        { status: 409 }
      );
    }

    const userId = crypto.randomUUID();
    const passwordHash = await hash(payload.password, 10);

    await db.insert(users).values({
      id: userId,
      name: payload.name,
      email: payload.email,
      passwordHash,
      systemRole: payload.systemRole,
      activeOrganizationId: payload.memberships[0]?.organizationId ?? null
    });

    await db.insert(memberships).values(buildMembershipValues(userId, payload.memberships));

    return NextResponse.json(
      {
        success: true,
        message: 'User created successfully'
      },
      { status: 201 }
    );
  } catch (error) {
    if (error instanceof AuthError) {
      return NextResponse.json({ message: error.message }, { status: error.status });
    }

    if (error instanceof Error) {
      return NextResponse.json({ message: error.message }, { status: 400 });
    }

    return NextResponse.json({ message: 'Unable to create user' }, { status: 500 });
  }
}
