import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { FastifyReply } from 'fastify';
import * as bcrypt from 'bcrypt';
import { PrismaService } from '../prisma/prisma.service';
import { UsersService } from '../users/users.service';
import { CreateUserDto } from '../users/dto/create-user.dto';
import { JwtPayload } from './types/jwt-payload.type';
import { LoginDto } from './dto/login.dto';

const REFRESH_COOKIE = 'refresh_token';

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

  async register(dto: CreateUserDto, reply: FastifyReply) {
    const existingUser = await this.usersService.findByEmail(dto.email);
    if (existingUser) {
      throw new BadRequestException('Email already registered');
    }

    const hashedPassword = await bcrypt.hash(dto.password, 10);
    const user = await this.usersService.createWithCompany(dto, hashedPassword);

    const tokens = await this.issueTokens(user);
    await this.updateRefreshToken(user.id, tokens.refreshToken);
    this.attachRefreshTokenCookie(reply, tokens.refreshToken);

    return { accessToken: tokens.accessToken };
  }

  async login(dto: LoginDto, reply: FastifyReply) {
    const user = await this.usersService.findByEmailWithCompany(dto.email);
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

    const passwordMatches = await bcrypt.compare(dto.password, user.password);
    if (!passwordMatches) {
      throw new UnauthorizedException('Invalid credentials');
    }

    const tokens = await this.issueTokens(user);
    await this.updateRefreshToken(user.id, tokens.refreshToken);
    this.attachRefreshTokenCookie(reply, tokens.refreshToken);

    return { accessToken: tokens.accessToken };
  }

  async refreshTokens(userId: number, refreshToken: string, reply: FastifyReply) {
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      include: { company: true },
    });

    if (!user || !user.refreshToken) {
      throw new UnauthorizedException('Refresh token not found');
    }

    const refreshMatches = await bcrypt.compare(refreshToken, user.refreshToken);
    if (!refreshMatches) {
      throw new UnauthorizedException('Refresh token invalid');
    }

    const tokens = await this.issueTokens(user);
    await this.updateRefreshToken(user.id, tokens.refreshToken);
    this.attachRefreshTokenCookie(reply, tokens.refreshToken);

    return { accessToken: tokens.accessToken };
  }

  async logout(userId: number, reply: FastifyReply) {
    await this.prisma.user.update({
      where: { id: userId },
      data: { refreshToken: null },
    });

    reply.clearCookie(REFRESH_COOKIE, { path: '/' });
    return { success: true };
  }

  private async issueTokens(user: any) {
    const payload: JwtPayload = {
      sub: user.id,
      email: user.email,
      companyId: user.companyId,
      companyStatus: user.company?.status,
      role: user.role,
    };

    const [accessToken, refreshToken] = await Promise.all([
      this.jwtService.signAsync(payload, {
        secret: this.configService.get<string>('JWT_ACCESS_SECRET') || 'access-secret',
        expiresIn: this.configService.get<string>('JWT_ACCESS_EXPIRES', '15m'),
      }),
      this.jwtService.signAsync(payload, {
        secret: this.configService.get<string>('JWT_REFRESH_SECRET') || 'refresh-secret',
        expiresIn: this.configService.get<string>('JWT_REFRESH_EXPIRES', '7d'),
      }),
    ]);

    return { accessToken, refreshToken };
  }

  private async updateRefreshToken(userId: number, refreshToken: string) {
    const hashedRefresh = await bcrypt.hash(refreshToken, 10);
    await this.prisma.user.update({
      where: { id: userId },
      data: { refreshToken: hashedRefresh },
    });
  }

  attachRefreshTokenCookie(reply: FastifyReply, token: string) {
    reply.setCookie(REFRESH_COOKIE, token, {
      httpOnly: true,
      sameSite: 'lax',
      secure: this.configService.get<string>('NODE_ENV') === 'production',
      path: '/',
      maxAge: 60 * 60 * 24 * 7, // 7 days
    });
  }
}
