import {
  Injectable,
  ConflictException,
  UnauthorizedException,
  BadRequestException,
  NotFoundException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcrypt';
import { randomUUID } from 'crypto';
import { PrismaService } from '../../prisma/prisma.service';
import { SignupDto } from './dto/signup.dto';

const BCRYPT_ROUNDS = 10;

const DEFAULT_EXPENSE_CATEGORIES = [
  { nameKo: 'SaaS 구독', nameEn: 'SaaS Subscription', slug: 'saas' },
  { nameKo: '외주비', nameEn: 'Outsourcing', slug: 'outsourcing' },
  { nameKo: '인프라', nameEn: 'Infrastructure', slug: 'infrastructure' },
  { nameKo: '마케팅', nameEn: 'Marketing', slug: 'marketing' },
  { nameKo: '사무용품', nameEn: 'Office Supplies', slug: 'office' },
  { nameKo: '접대비', nameEn: 'Entertainment', slug: 'entertainment' },
  { nameKo: '교육비', nameEn: 'Education', slug: 'education' },
  { nameKo: '소프트웨어', nameEn: 'Software', slug: 'software' },
  { nameKo: '교통비', nameEn: 'Transportation', slug: 'transport' },
];

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

  async signup(dto: SignupDto) {
    const existing = await this.prisma.user.findUnique({ where: { email: dto.email } });
    if (existing) throw new ConflictException('Email already registered');

    const passwordHash = await bcrypt.hash(dto.password, BCRYPT_ROUNDS);
    const emailVerifyToken = randomUUID();

    let freePlan = await this.prisma.plan.findUnique({ where: { planId: 'free' } });
    if (!freePlan) {
      freePlan = await this.prisma.plan.findFirst({ orderBy: { price: 'asc' } });
    }

    const user = await this.prisma.$transaction(async (tx) => {
      const newUser = await tx.user.create({
        data: { email: dto.email, passwordHash, name: dto.name, emailVerifyToken },
      });

      await tx.business.create({
        data: { userId: newUser.id, businessName: dto.businessName },
      });

      await tx.creditSettings.create({
        data: { userId: newUser.id, balance: 100 },
      });

      await tx.notificationSettings.create({
        data: { userId: newUser.id },
      });

      if (freePlan) {
        await tx.subscription.create({
          data: {
            userId: newUser.id,
            planId: freePlan.id,
            status: 'active',
            currentPeriodStart: new Date(),
          },
        });
      }

      await tx.expenseCategory.createMany({
        data: DEFAULT_EXPENSE_CATEGORIES.map((cat) => ({
          userId: newUser.id,
          nameKo: cat.nameKo,
          nameEn: cat.nameEn,
          slug: cat.slug,
          isSystem: true,
        })),
      });

      return newUser;
    });

    const tokens = await this.generateTokens(user);
    return { user: { id: user.id, email: user.email, name: user.name }, ...tokens };
  }

  async login(email: string, password: string) {
    const user = await this.validateUser(email, password);
    if (!user) throw new UnauthorizedException('Invalid email or password');
    const tokens = await this.generateTokens(user);
    return { user: { id: user.id, email: user.email, name: user.name }, ...tokens };
  }

  async validateUser(email: string, password: string) {
    const user = await this.prisma.user.findUnique({ where: { email } });
    if (!user || !user.isActive) return null;
    const valid = await bcrypt.compare(password, user.passwordHash);
    if (!valid) return null;
    const { passwordHash, refreshToken, ...result } = user;
    return result;
  }

  async generateTokens(user: { id: number; email: string }) {
    const payload = { sub: user.id, email: user.email };
    const accessToken = this.jwtService.sign(payload as any, {
      secret: this.configService.get<string>('JWT_SECRET') || 'fallback-secret',
      expiresIn: this.configService.get<string>('JWT_EXPIRATION', '15m') as any,
    });
    const refreshTokenRaw = randomUUID();
    const hashed = await bcrypt.hash(refreshTokenRaw, BCRYPT_ROUNDS);
    await this.prisma.user.update({ where: { id: user.id }, data: { refreshToken: hashed } });
    return { accessToken, refreshToken: refreshTokenRaw };
  }

  async refreshToken(userId: number, refreshToken: string) {
    const user = await this.prisma.user.findUnique({ where: { id: userId } });
    if (!user || !user.isActive || !user.refreshToken) {
      throw new UnauthorizedException('Access denied');
    }
    const valid = await bcrypt.compare(refreshToken, user.refreshToken);
    if (!valid) {
      await this.prisma.user.update({ where: { id: user.id }, data: { refreshToken: null } });
      throw new UnauthorizedException('Invalid refresh token');
    }
    return this.generateTokens(user);
  }

  async logout(userId: number) {
    await this.prisma.user.update({ where: { id: userId }, data: { refreshToken: null } });
    return { message: 'Logged out successfully' };
  }

  async forgotPassword(email: string) {
    const user = await this.prisma.user.findUnique({ where: { email } });
    if (!user) return { message: 'If the email exists, a reset link has been sent' };
    const resetToken = randomUUID();
    const resetExpires = new Date(Date.now() + 60 * 60 * 1000);
    await this.prisma.user.update({
      where: { id: user.id },
      data: { passwordResetToken: resetToken, passwordResetExpires: resetExpires },
    });
    return { message: 'If the email exists, a reset link has been sent', token: resetToken };
  }

  async resetPassword(token: string, newPassword: string) {
    const user = await this.prisma.user.findFirst({
      where: { passwordResetToken: token, passwordResetExpires: { gt: new Date() } },
    });
    if (!user) throw new BadRequestException('Invalid or expired reset token');
    const passwordHash = await bcrypt.hash(newPassword, BCRYPT_ROUNDS);
    await this.prisma.user.update({
      where: { id: user.id },
      data: { passwordHash, passwordResetToken: null, passwordResetExpires: null, refreshToken: null },
    });
    return { message: 'Password reset successfully' };
  }

  async verifyEmail(token: string) {
    const user = await this.prisma.user.findFirst({ where: { emailVerifyToken: token } });
    if (!user) throw new BadRequestException('Invalid verification token');
    if (user.emailVerified) return { message: 'Email already verified' };
    await this.prisma.user.update({
      where: { id: user.id },
      data: { emailVerified: true, emailVerifyToken: null },
    });
    return { message: 'Email verified successfully' };
  }

  async getProfile(userId: number) {
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      include: { business: true, subscription: { include: { plan: true } }, creditSettings: true },
    });
    if (!user) throw new NotFoundException('User not found');
    const { passwordHash, refreshToken, emailVerifyToken, passwordResetToken, passwordResetExpires, ...profile } = user;
    return profile;
  }
}
