import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Inject,
  InternalServerErrorException,
  NotFoundException,
  Param,
  Patch,
  Post,
  UseGuards,
} from '@nestjs/common';
import { eq, type Db, users as usersTable } from '@mosaicstack/db';
import type { Auth } from '@mosaicstack/auth';
import { AUTH } from '../auth/auth.tokens.js';
import { DB } from '../database/database.module.js';
import { AdminGuard } from './admin.guard.js';
import type {
  BanUserDto,
  CreateUserDto,
  UpdateUserRoleDto,
  UserDto,
  UserListDto,
} from './admin.dto.js';

type UserRow = typeof usersTable.$inferSelect;

function toUserDto(u: UserRow): UserDto {
  return {
    id: u.id,
    name: u.name,
    email: u.email,
    role: u.role,
    banned: u.banned ?? false,
    banReason: u.banReason ?? null,
    createdAt: u.createdAt.toISOString(),
    updatedAt: u.updatedAt.toISOString(),
  };
}

async function requireUpdated(
  db: Db,
  id: string,
  update: Partial<Omit<UserRow, 'id' | 'createdAt'>>,
): Promise<UserDto> {
  const [updated] = await db
    .update(usersTable)
    .set({ ...update, updatedAt: new Date() })
    .where(eq(usersTable.id, id))
    .returning();
  if (!updated) throw new InternalServerErrorException('Update returned no rows');
  return toUserDto(updated);
}

@Controller('api/admin/users')
@UseGuards(AdminGuard)
export class AdminController {
  constructor(
    @Inject(DB) private readonly db: Db,
    @Inject(AUTH) private readonly auth: Auth,
  ) {}

  @Get()
  async listUsers(): Promise<UserListDto> {
    const rows = await this.db.select().from(usersTable).orderBy(usersTable.createdAt);
    const userList: UserDto[] = rows.map(toUserDto);
    return { users: userList, total: userList.length };
  }

  @Get(':id')
  async getUser(@Param('id') id: string): Promise<UserDto> {
    const [user] = await this.db.select().from(usersTable).where(eq(usersTable.id, id)).limit(1);
    if (!user) throw new NotFoundException('User not found');
    return toUserDto(user);
  }

  @Post()
  async createUser(@Body() body: CreateUserDto): Promise<UserDto> {
    // Use auth API to create user so password is properly hashed
    const authApi = this.auth.api as unknown as {
      createUser: (opts: {
        body: { name: string; email: string; password: string; role?: string };
      }) => Promise<{
        user: { id: string; name: string; email: string; createdAt: unknown; updatedAt: unknown };
      }>;
    };

    const result = await authApi.createUser({
      body: {
        name: body.name,
        email: body.email,
        password: body.password,
        role: body.role ?? 'member',
      },
    });

    // Re-fetch from DB to get full row with our schema
    const [user] = await this.db
      .select()
      .from(usersTable)
      .where(eq(usersTable.id, result.user.id))
      .limit(1);

    if (!user) throw new InternalServerErrorException('User created but not found in DB');
    return toUserDto(user);
  }

  @Patch(':id/role')
  async setRole(@Param('id') id: string, @Body() body: UpdateUserRoleDto): Promise<UserDto> {
    await this.ensureExists(id);
    return requireUpdated(this.db, id, { role: body.role });
  }

  @Post(':id/ban')
  @HttpCode(HttpStatus.OK)
  async banUser(@Param('id') id: string, @Body() body: BanUserDto): Promise<UserDto> {
    await this.ensureExists(id);
    return requireUpdated(this.db, id, { banned: true, banReason: body.reason ?? null });
  }

  @Post(':id/unban')
  @HttpCode(HttpStatus.OK)
  async unbanUser(@Param('id') id: string): Promise<UserDto> {
    await this.ensureExists(id);
    return requireUpdated(this.db, id, { banned: false, banReason: null, banExpires: null });
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async deleteUser(@Param('id') id: string): Promise<void> {
    await this.ensureExists(id);
    await this.db.delete(usersTable).where(eq(usersTable.id, id));
  }

  private async ensureExists(id: string): Promise<void> {
    const [existing] = await this.db
      .select({ id: usersTable.id })
      .from(usersTable)
      .where(eq(usersTable.id, id))
      .limit(1);
    if (!existing) throw new NotFoundException('User not found');
  }
}
