import { ConflictException, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcryptjs';
import { PrismaService } from '../prisma/prisma.service';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { JwtPayload } from './strategies/jwt.strategy';

const BCRYPT_ROUNDS = 12;

function slugify(name: string): string {
  return name
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 48) || 'org';
}

@Injectable()
export class AuthService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly jwtService: JwtService,
  ) {}

  async register(dto: RegisterDto) {
    const existing = await this.prisma.user.findUnique({ where: { email: dto.email } });
    if (existing) {
      throw new ConflictException('An account with this email already exists');
    }

    const passwordHash = await bcrypt.hash(dto.password, BCRYPT_ROUNDS);
    const baseSlug = slugify(dto.orgName);

    const { user } = await this.prisma.$transaction(async (tx) => {
      let slug = baseSlug;
      if (await tx.organization.findUnique({ where: { slug } })) {
        slug = `${baseSlug}-${Math.random().toString(36).slice(2, 8)}`;
      }
      const org = await tx.organization.create({ data: { name: dto.orgName, slug } });
      const user = await tx.user.create({
        data: { email: dto.email, name: dto.name, passwordHash },
      });
      await tx.orgMembership.create({
        data: { orgId: org.id, userId: user.id, role: 'ORG_ADMIN' },
      });
      return { user, org };
    });

    return this.sessionFor(user.id);
  }

  async login(dto: LoginDto) {
    const user = await this.prisma.user.findUnique({ where: { email: dto.email } });
    if (!user || !(await bcrypt.compare(dto.password, user.passwordHash))) {
      throw new UnauthorizedException('Invalid email or password');
    }
    return this.sessionFor(user.id);
  }

  async me(userId: string) {
    const user = await this.prisma.user.findUniqueOrThrow({
      where: { id: userId },
      select: { id: true, email: true, name: true, createdAt: true },
    });
    const memberships = await this.membershipsOf(userId);
    return { user, memberships };
  }

  private async sessionFor(userId: string) {
    const user = await this.prisma.user.findUniqueOrThrow({
      where: { id: userId },
      select: { id: true, email: true, name: true },
    });
    const payload: JwtPayload = { sub: user.id, email: user.email };
    const token = this.jwtService.sign(payload);
    const memberships = await this.membershipsOf(userId);
    return { token, user, memberships };
  }

  private membershipsOf(userId: string) {
    return this.prisma.orgMembership.findMany({
      where: { userId },
      select: {
        role: true,
        org: { select: { id: true, name: true, slug: true } },
      },
      orderBy: { createdAt: 'asc' },
    });
  }
}
