import prisma from "../config/database";
import { Prisma } from "@prisma/client";
import bcrypt from "bcryptjs";
import { config } from "../config/env";

const ALLOWED_SORT_FIELDS = [
  "id",
  "username",
  "email",
  "first_name",
  "last_name",
  "created_at",
];

const USER_SELECT = {
  id: true,
  username: true,
  email: true,
  first_name: true,
  last_name: true,
  role_id: true,
  is_active: true,
  last_login: true,
  totp_enabled: true,
  created_at: true,
  updated_at: true,
  roles: { select: { id: true, name: true, display_name: true } },
} as const;

export interface ListUsersParams {
  page: number;
  limit: number;
  skip: number;
  sort: string;
  order: "asc" | "desc";
  search: string;
  permission?: string;
}

export interface CreateUserData {
  username: string;
  email: string;
  password: string;
  first_name: string;
  last_name: string;
  role_id?: number | null;
  is_active?: boolean;
}

export interface UpdateUserData {
  username?: string;
  email?: string;
  password?: string;
  first_name?: string;
  last_name?: string;
  role_id?: number | null;
  is_active?: boolean | number | string;
}

export async function listUsers(params: ListUsersParams) {
  const { page, limit, skip, sort, order, search, permission } = params;
  const sortField = ALLOWED_SORT_FIELDS.includes(sort) ? sort : "id";

  const where: any = search
    ? {
        OR: [
          { username: { contains: search } },
          { email: { contains: search } },
          { first_name: { contains: search } },
          { last_name: { contains: search } },
        ],
      }
    : {};

  if (permission) {
    where.roles = {
      role_permissions: {
        some: { permissions: { name: permission } },
      },
    };
  }

  const [users, total] = await Promise.all([
    prisma.users.findMany({
      where,
      skip,
      take: limit,
      orderBy: { [sortField]: order },
      select: USER_SELECT,
    }),
    prisma.users.count({ where }),
  ]);

  return { users, total, page, limit };
}

export async function getUser(id: number) {
  return prisma.users.findUnique({
    where: { id },
    select: USER_SELECT,
  });
}

export async function createUser(
  data: CreateUserData,
  callerRoleName?: string,
) {
  const username = data.username.trim();
  const email = data.email.trim();
  const firstName = data.first_name.trim();
  const lastName = data.last_name.trim();

  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
    return { error: "Neplatný formát e-mailu", status: 400 } as const;
  }

  // Enforce min password length in-service (consistent with updateUser),
  // independent of any schema-level check.
  if (data.password.length < 8) {
    return { error: "Heslo musí mít alespoň 8 znaků", status: 400 } as const;
  }

  if (data.role_id) {
    const targetRole = await prisma.roles.findUnique({
      where: { id: Number(data.role_id) },
    });
    if (targetRole?.name === "admin" && callerRoleName !== "admin") {
      return { error: "Nelze přiřadit roli admin", status: 403 } as const;
    }
  }

  const passwordHash = await bcrypt.hash(
    data.password,
    config.security.bcryptCost,
  );

  try {
    // Run the uniqueness checks INSIDE the transaction with the create so two
    // concurrent creates of the same username/email return the intended 409
    // instead of letting the DB unique constraint throw an unhandled 500
    // (mirrors updateUser).
    const user = await prisma.$transaction(async (tx) => {
      const existingUsername = await tx.users.findFirst({
        where: { username },
      });
      if (existingUsername) {
        throw Object.assign(new Error("Uživatelské jméno již existuje"), {
          status: 409,
        });
      }

      const existingEmail = await tx.users.findFirst({ where: { email } });
      if (existingEmail) {
        throw Object.assign(new Error("E-mail již existuje"), { status: 409 });
      }

      return tx.users.create({
        data: {
          username,
          email,
          password_hash: passwordHash,
          first_name: firstName,
          last_name: lastName,
          role_id: data.role_id ? Number(data.role_id) : null,
          is_active: data.is_active !== false,
        },
      });
    });

    return { user } as const;
  } catch (err) {
    if (err instanceof Error && "status" in err) {
      return {
        error: err.message,
        status: (err as Error & { status: number }).status,
      } as const;
    }
    // A concurrent insert that beat the in-transaction uniqueness check surfaces
    // as a P2002 unique-constraint violation — return the intended 409, not 500.
    if (
      err instanceof Prisma.PrismaClientKnownRequestError &&
      err.code === "P2002"
    ) {
      return {
        error: "Uživatelské jméno nebo e-mail již existuje",
        status: 409,
      } as const;
    }
    throw err;
  }
}

export async function updateUser(
  id: number,
  body: UpdateUserData,
  callerRoleName?: string,
) {
  const existing = await prisma.users.findUnique({ where: { id } });
  if (!existing) {
    return { error: "Uživatel nenalezen", status: 404 } as const;
  }

  if (body.role_id !== undefined) {
    if (body.role_id) {
      const targetRole = await prisma.roles.findUnique({
        where: { id: Number(body.role_id) },
      });
      if (targetRole?.name === "admin" && callerRoleName !== "admin") {
        return { error: "Nelze přiřadit roli admin", status: 403 } as const;
      }
    }

    if (
      existing.role_id !== null &&
      Number(body.role_id) !== existing.role_id
    ) {
      const currentRole = await prisma.roles.findUnique({
        where: { id: existing.role_id },
      });
      if (currentRole?.name === "admin") {
        const adminRole = await prisma.roles.findFirst({
          where: { name: "admin" },
        });
        if (adminRole) {
          const activeAdminCount = await prisma.users.count({
            where: { role_id: adminRole.id, is_active: true },
          });
          if (activeAdminCount <= 1) {
            return {
              error: "Nelze odebrat roli poslednímu aktivnímu administrátorovi",
              status: 400,
            } as const;
          }
        }
      }
    }
  }

  if (body.is_active !== undefined && !body.is_active) {
    if (existing.role_id !== null && existing.is_active) {
      const adminRole = await prisma.roles.findFirst({
        where: { name: "admin" },
      });
      if (adminRole && existing.role_id === adminRole.id) {
        const activeAdminCount = await prisma.users.count({
          where: { role_id: adminRole.id, is_active: true },
        });
        if (activeAdminCount <= 1) {
          return {
            error: "Nelze deaktivovat posledního aktivního administrátora",
            status: 400,
          } as const;
        }
      }
    }
  }

  const data: Record<string, unknown> = {};

  try {
    await prisma.$transaction(async (tx) => {
      if (body.username !== undefined) {
        const newUsername = String(body.username).trim();
        if (newUsername !== existing.username) {
          const existingUsername = await tx.users.findFirst({
            where: { username: newUsername },
          });
          if (existingUsername) {
            throw Object.assign(new Error("Uživatelské jméno již existuje"), {
              status: 409,
            });
          }
        }
        data.username = newUsername;
      }

      if (body.email !== undefined) {
        const newEmail = String(body.email).trim();
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
          throw Object.assign(new Error("Neplatný formát e-mailu"), {
            status: 400,
          });
        }
        const existingEmail = await tx.users.findFirst({
          where: { email: newEmail, id: { not: id } },
        });
        if (existingEmail) {
          throw Object.assign(new Error("E-mail již existuje"), {
            status: 409,
          });
        }
        data.email = newEmail;
      }

      if (body.first_name !== undefined)
        data.first_name = String(body.first_name);
      if (body.last_name !== undefined) data.last_name = String(body.last_name);
      if (body.role_id !== undefined)
        data.role_id = body.role_id ? Number(body.role_id) : null;
      if (body.is_active !== undefined)
        data.is_active =
          body.is_active === true ||
          body.is_active === 1 ||
          body.is_active === "1";
      if (body.password) {
        const newPassword = String(body.password);
        if (newPassword.length < 8) {
          throw Object.assign(new Error("Heslo musí mít alespoň 8 znaků"), {
            status: 400,
          });
        }
        data.password_hash = await bcrypt.hash(
          newPassword,
          config.security.bcryptCost,
        );
        data.password_changed_at = new Date();
      }

      await tx.users.update({ where: { id }, data });
    });
  } catch (err) {
    if (err instanceof Error && "status" in err) {
      return {
        error: err.message,
        status: (err as Error & { status: number }).status,
      } as const;
    }
    throw err;
  }

  return { id, username: existing.username } as const;
}

export async function deleteUser(id: number, currentUserId?: number) {
  if (id === currentUserId) {
    return { error: "Nelze smazat vlastní účet", status: 400 } as const;
  }

  const existing = await prisma.users.findUnique({ where: { id } });
  if (!existing) {
    return { error: "Uživatel nenalezen", status: 404 } as const;
  }

  await prisma.$transaction(async (tx) => {
    await tx.refresh_tokens.deleteMany({ where: { user_id: id } });
    await tx.users.delete({ where: { id } });
  });

  return { id, username: existing.username } as const;
}
