import {
  Injectable,
  UnauthorizedException,
  ConflictException,
  NotFoundException,
  BadRequestException,
  Logger,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcryptjs';
import { randomUUID } from 'crypto';
import { PrismaService } from '@chatapp/database';
import { RegisterDto, LoginDto, RefreshDto, Login2faDto } from './dto';
import { TwoFactorService } from './two-factor.service';

@Injectable()
export class AuthService {
  private readonly logger = new Logger(AuthService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly jwtService: JwtService,
    private readonly configService: ConfigService,
    private readonly twoFactorService: TwoFactorService,
  ) {}

  async register(dto: RegisterDto) {
    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, 12);

    const user = await this.prisma.user.create({
      data: {
        email: dto.email,
        passwordHash,
        displayName: dto.display_name,
        timezone: 'UTC',
        locale: 'en',
      },
    });

    let workspace = null;
    if (dto.workspace_name) {
      const domain = this.slugify(dto.workspace_name);
      workspace = await this.prisma.workspace.create({
        data: {
          name: dto.workspace_name,
          domain,
          plan: 'free',
          maxMembers: 100,
          allowCreateChannels: true,
          allowCustomEmoji: true,
          allowSharedChannels: false,
          enforce2fa: false,
          ssoRequired: false,
          createdBy: user.id,
          members: {
            create: {
              userId: user.id,
              role: 'owner',
              joinedAt: new Date(),
            },
          },
          settings: {
            create: {
              defaultChannel: 'general',
              allowMessageDeletion: true,
              requireLinkPreviews: true,
            },
          },
        },
      });

      await this.createDefaultChannels(workspace.id, user.id);
    }

    const tokens = await this.generateTokens(user.id, user.email);

    return {
      user: {
        id: user.id,
        email: user.email,
        display_name: user.displayName,
        avatar_url: user.avatarUrl,
      },
      workspace: workspace
        ? {
            id: workspace.id,
            name: workspace.name,
            domain: workspace.domain,
          }
        : null,
      access_token: tokens.accessToken,
      refresh_token: tokens.refreshToken,
    };
  }

  async login(dto: LoginDto) {
    const user = await this.prisma.user.findUnique({
      where: { email: dto.email },
    });
    if (!user || user.isDeleted) {
      throw new UnauthorizedException('Invalid credentials');
    }

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

    // Verificar si el usuario tiene 2FA habilitado
    const twoFactorRecord = await this.prisma.twoFactorAuth.findUnique({
      where: { userId: user.id },
    });
    if (twoFactorRecord?.enabled) {
      return { requires_2fa: true, email: user.email, userId: user.id };
    }

    const memberships = await this.prisma.workspaceMember.findMany({
      where: { userId: user.id, deactivatedAt: null },
      include: { workspace: true },
    });

    const workspaces = memberships.map((m) => ({
      id: m.workspace.id,
      name: m.workspace.name,
      domain: m.workspace.domain,
      role: m.role,
    }));

    const tokens = await this.generateTokens(user.id, user.email);

    await this.createSession(user.id, tokens.refreshToken, 'web');

    return {
      user: {
        id: user.id,
        email: user.email,
        display_name: user.displayName,
        avatar_url: user.avatarUrl,
      },
      workspaces,
      access_token: tokens.accessToken,
      refresh_token: tokens.refreshToken,
    };
  }

  async loginWith2fa(dto: Login2faDto) {
    const user = await this.prisma.user.findUnique({
      where: { email: dto.email },
    });
    if (!user || user.isDeleted) {
      throw new UnauthorizedException('Invalid credentials');
    }

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

    const twoFactorRecord = await this.prisma.twoFactorAuth.findUnique({
      where: { userId: user.id },
    });
    if (!twoFactorRecord?.enabled) {
      throw new UnauthorizedException('2FA not enabled for this user');
    }

    // Verifica el codigo TOTP (o backup code)
    await this.twoFactorService.verifyToken(user.id, dto.code);

    const memberships = await this.prisma.workspaceMember.findMany({
      where: { userId: user.id, deactivatedAt: null },
      include: { workspace: true },
    });

    const workspaces = memberships.map((m) => ({
      id: m.workspace.id,
      name: m.workspace.name,
      domain: m.workspace.domain,
      role: m.role,
    }));

    const tokens = await this.generateTokens(user.id, user.email);

    await this.createSession(user.id, tokens.refreshToken, 'web');

    return {
      user: {
        id: user.id,
        email: user.email,
        display_name: user.displayName,
        avatar_url: user.avatarUrl,
      },
      workspaces,
      access_token: tokens.accessToken,
      refresh_token: tokens.refreshToken,
    };
  }

  async refresh(dto: RefreshDto) {
    try {
      const payload = await this.jwtService.verifyAsync(dto.refresh_token, {
        secret: this.configService.get<string>('JWT_SECRET'),
      });

      const session = await this.prisma.session.findFirst({
        where: {
          userId: payload.userId,
          revokedAt: null,
          expiresAt: { gt: new Date() },
        },
      });

      if (!session) {
        throw new UnauthorizedException('Session not found');
      }

      await this.prisma.session.update({
        where: { id: session.id },
        data: { revokedAt: new Date() },
      });

      const user = await this.prisma.user.findUnique({
        where: { id: payload.userId },
      });
      if (!user || user.isDeleted) {
        throw new UnauthorizedException('User not found');
      }

      const tokens = await this.generateTokens(user.id, user.email);
      await this.createSession(user.id, tokens.refreshToken, 'web');

      return tokens;
    } catch {
      throw new UnauthorizedException('Invalid refresh token');
    }
  }

  async logout(userId: string) {
    await this.prisma.session.updateMany({
      where: { userId, revokedAt: null },
      data: { revokedAt: new Date() },
    });
    return { message: 'Logged out successfully' };
  }

  async verifyToken(token: string): Promise<{ userId: string; email: string }> {
    try {
      const payload = await this.jwtService.verifyAsync(token, {
        secret: this.configService.get<string>('JWT_SECRET'),
      });
      return { userId: payload.userId, email: payload.email };
    } catch {
      throw new UnauthorizedException('Invalid token');
    }
  }

  private async generateTokens(userId: string, email: string) {
    const accessToken = await this.jwtService.signAsync(
      { userId, email },
      { expiresIn: this.configService.get<string>('JWT_ACCESS_EXPIRES', '15m') as any },
    );

    const refreshToken = await this.jwtService.signAsync(
      { userId, email, type: 'refresh' },
      { expiresIn: this.configService.get<string>('JWT_REFRESH_EXPIRES', '30d') as any },
    );

    return { accessToken, refreshToken };
  }

  private async createSession(userId: string, refreshToken: string, deviceType: string) {
    const tokenHash = await bcrypt.hash(refreshToken, 10);
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 30);

    await this.prisma.session.create({
      data: {
        userId,
        tokenHash,
        deviceType,
        expiresAt,
      },
    });
  }

  private async createDefaultChannels(workspaceId: string, userId: string) {
    const channels = [
      { name: 'general', handle: 'general', type: 'public', isDefault: true },
      { name: 'random', handle: 'random', type: 'public', isDefault: false },
    ];

    for (const ch of channels) {
      const channel = await this.prisma.channel.create({
        data: {
          workspaceId,
          name: ch.name,
          handle: ch.handle,
          type: ch.type,
          isDefault: ch.isDefault,
          createdBy: userId,
        },
      });

      await this.prisma.channelMember.create({
        data: {
          channelId: channel.id,
          userId,
          workspaceId,
          role: 'owner',
          notificationPref: 'all',
        },
      });
    }
  }

  private slugify(text: string): string {
    return text
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/^-+|-+$/g, '')
      .substring(0, 50);
  }
}
