import bcrypt from "bcryptjs";

import type { AuthSession, AuthUser, UserPreferences, UserRole } from "@rental/shared";

import { env } from "../../config/env.js";
import { query } from "../../db/pool.js";
import { ConflictError, NotFoundError, UnauthorizedError } from "../../utils/app-error.js";
import { signAuthToken } from "../../middleware/auth.js";

type UserRow = {
  id: string;
  name: string;
  email: string;
  password_hash: string;
  role: UserRole;
  is_active: boolean;
  preferences?: unknown;
};

const normalizePreferences = (value: unknown): UserPreferences => {
  if (!value || typeof value !== "object") {
    return {
      equipmentCategoryOrder: [],
      equipmentItemOrder: []
    };
  }

  const source = value as {
    equipmentCategoryOrder?: unknown;
    equipmentItemOrder?: unknown;
  };

  return {
    equipmentCategoryOrder: Array.isArray(source.equipmentCategoryOrder)
      ? source.equipmentCategoryOrder.filter((entry): entry is string => typeof entry === "string")
      : [],
    equipmentItemOrder: Array.isArray(source.equipmentItemOrder)
      ? source.equipmentItemOrder.filter((entry): entry is string => typeof entry === "string")
      : []
  };
};

export const mapAuthUser = (row: Pick<UserRow, "id" | "name" | "email" | "role" | "is_active">): AuthUser => ({
  id: row.id,
  name: row.name,
  email: row.email,
  role: row.role,
  isActive: row.is_active
});

export const ensureDefaultAdmin = async () => {
  const passwordHash = await bcrypt.hash(env.ADMIN_PASSWORD, 10);

  await query(
    `
      insert into users (name, email, password_hash, role, is_active)
      values ($1, $2, $3, 'admin', true)
      on conflict (email) do update
        set name = excluded.name,
            password_hash = excluded.password_hash,
            role = 'admin',
            is_active = true,
            updated_at = now()
    `,
    [env.ADMIN_NAME, env.ADMIN_EMAIL, passwordHash]
  );
};

export const getUserPreferences = async (id: string): Promise<UserPreferences> => {
  const result = await query<Pick<UserRow, "preferences">>(
    `
      select preferences
      from users
      where id = $1
    `,
    [id]
  );

  return normalizePreferences(result.rows[0]?.preferences);
};

export const updateUserPreferences = async (
  id: string,
  payload: Partial<UserPreferences>
): Promise<UserPreferences> => {
  const currentPreferences = await getUserPreferences(id);
  const nextPreferences: UserPreferences = {
    equipmentCategoryOrder: payload.equipmentCategoryOrder ?? currentPreferences.equipmentCategoryOrder,
    equipmentItemOrder: payload.equipmentItemOrder ?? currentPreferences.equipmentItemOrder
  };

  const result = await query<Pick<UserRow, "preferences">>(
    `
      update users
      set
        preferences = $2::jsonb,
        updated_at = now()
      where id = $1
      returning preferences
    `,
    [id, JSON.stringify(nextPreferences)]
  );

  return normalizePreferences(result.rows[0]?.preferences);
};

export const login = async (email: string, password: string): Promise<AuthSession> => {
  const result = await query<UserRow>(
    `
      select id, name, email, password_hash, role, is_active
      from users
      where email = $1
    `,
    [email]
  );

  const user = result.rows[0];

  if (!user) {
    throw new UnauthorizedError("Invalid credentials");
  }

  if (!user.is_active) {
    throw new ConflictError("User account is inactive");
  }

  const matches = await bcrypt.compare(password, user.password_hash);

  if (!matches) {
    throw new UnauthorizedError("Invalid credentials");
  }

  const authUser = mapAuthUser(user);

  return {
    token: signAuthToken(authUser),
    user: authUser
  };
};

export const getAuthUserById = async (id: string) => {
  const result = await query<Pick<UserRow, "id" | "name" | "email" | "role" | "is_active">>(
    `
      select id, name, email, role, is_active
      from users
      where id = $1
    `,
    [id]
  );

  const user = result.rows[0];

  if (!user) {
    throw new NotFoundError("User not found");
  }

  return mapAuthUser(user);
};
