import {
  Injectable,
  UnauthorizedException,
  ConflictException,
  BadRequestException,
  NotFoundException,
  NotImplementedException,
  Logger,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import * as argon2 from 'argon2';
import * as OTPAuth from 'otpauth';
import * as QRCode from 'qrcode';
import { randomBytes } from 'crypto';
import { User } from '../../entities/user.entity';
import { License } from '../../entities/license.entity';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';
import { UpdateProfileDto } from './dto/update-profile.dto';

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

  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(License)
    private licenseRepository: Repository<License>,
    private jwtService: JwtService,
    private configService: ConfigService,
  ) {}

  async register(dto: RegisterDto) {
    // Normalize email to lowercase to prevent case-sensitive duplicates
    const normalizedEmail = dto.email.toLowerCase();

    // Check if user already exists
    const existingUser = await this.userRepository
      .createQueryBuilder('user')
      .where('LOWER(user.email) = :email OR user.username = :username', {
        email: normalizedEmail,
        username: dto.username,
      })
      .getOne();

    if (existingUser) {
      if (existingUser.email.toLowerCase() === normalizedEmail) {
        throw new ConflictException('Email already registered');
      }
      throw new ConflictException('Username already taken');
    }

    // Hash password
    const password_hash = await argon2.hash(dto.password);

    // Create user (email stored lowercase)
    const user = this.userRepository.create({
      email: normalizedEmail,
      username: dto.username,
      password_hash,
      email_verified: false,
      is_super_admin: false,
    });

    await this.userRepository.save(user);

    // Create license for the user
    const licenseKey = this.generateLicenseKey();
    const license = this.licenseRepository.create({
      license_key: licenseKey,
      owner_user_id: user.id,
      status: 'active',
      modules_enabled: [],
      webstore_active: false,
    });

    await this.licenseRepository.save(license);

    // Generate tokens (include license_id for tenant-scoped operations)
    const tokens = await this.generateTokens(user, license.id);

    return {
      ...tokens,
      requires_totp: false,
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
        is_super_admin: user.is_super_admin,
        totp_enabled: user.totp_enabled,
      },
      license: {
        id: license.id,
        license_key: license.license_key,
        status: license.status,
        server_name: license.server_name ?? null,
        subdomain: license.subdomain ?? null,
        custom_domain: license.custom_domain ?? null,
        modules_enabled: license.modules_enabled,
        webstore_active: license.webstore_active,
        created_at: license.created_at,
        expires_at: license.expires_at ?? null,
      },
    };
  }

  async login(dto: LoginDto) {
    // Find user by email (case-insensitive)
    const user = await this.userRepository
      .createQueryBuilder('user')
      .where('LOWER(user.email) = :email', { email: dto.email.toLowerCase() })
      .getOne();

    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

    // Verify password
    const passwordValid = await argon2.verify(user.password_hash, dto.password);
    if (!passwordValid) {
      throw new UnauthorizedException('Invalid credentials');
    }

    // Check TOTP if enabled
    if (user.totp_enabled) {
      if (!dto.totp_code) {
        return {
          requires_totp: true,
          user: {
            id: user.id,
            email: user.email,
            username: user.username,
          },
        };
      }

      const totpValid = await this.verifyTotpCode(user, dto.totp_code);
      if (!totpValid) {
        throw new UnauthorizedException('Invalid TOTP code');
      }
    }

    // Get user's license (needed for JWT license_id claim)
    const license = await this.licenseRepository.findOne({
      where: { owner_user_id: user.id },
    });

    // Generate tokens
    const tokens = await this.generateTokens(user, license?.id);

    return {
      ...tokens,
      requires_totp: false,
      user: {
        id: user.id,
        email: user.email,
        username: user.username,
        is_super_admin: user.is_super_admin,
        totp_enabled: user.totp_enabled,
      },
      license: license ? {
        id: license.id,
        license_key: license.license_key,
        status: license.status,
        server_name: license.server_name,
        subdomain: license.subdomain,
        custom_domain: license.custom_domain,
        modules_enabled: license.modules_enabled,
        webstore_active: license.webstore_active,
        created_at: license.created_at,
        expires_at: license.expires_at,
      } : null,
    };
  }

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

      const user = await this.userRepository.findOne({
        where: { id: payload.sub },
      });

      if (!user) {
        throw new UnauthorizedException('User not found');
      }

      // Look up license for JWT claim
      const license = await this.licenseRepository.findOne({
        where: { owner_user_id: user.id },
      });

      // Generate new token pair (rotating refresh tokens)
      const tokens = await this.generateTokens(user, license?.id);

      return {
        access_token: tokens.access_token,
        refresh_token: tokens.refresh_token,
      };
    } catch (error) {
      throw new UnauthorizedException('Invalid refresh token');
    }
  }

  async setupTotp(userId: string) {
    const user = await this.userRepository.findOne({
      where: { id: userId },
    });

    if (!user) {
      throw new NotFoundException('User not found');
    }

    if (user.totp_enabled) {
      throw new BadRequestException('TOTP already enabled');
    }

    // Generate TOTP secret
    const secret = new OTPAuth.Secret({ size: 20 });
    const totp = new OTPAuth.TOTP({
      issuer: 'Corrosion',
      label: user.email,
      algorithm: 'SHA1',
      digits: 6,
      period: 30,
      secret,
    });

    // Save secret to user (but don't enable yet)
    await this.userRepository.update(user.id, {
      totp_secret: secret.base32,
    });

    // Generate QR code
    const qrCode = await QRCode.toDataURL(totp.toString());

    return {
      qr_code: qrCode,
      secret: secret.base32,
    };
  }

  async verifyTotp(userId: string, code: string) {
    const user = await this.userRepository.findOne({
      where: { id: userId },
    });

    if (!user) {
      throw new NotFoundException('User not found');
    }

    if (!user.totp_secret) {
      throw new BadRequestException('TOTP not set up');
    }

    const valid = await this.verifyTotpCode(user, code);
    if (!valid) {
      throw new UnauthorizedException('Invalid TOTP code');
    }

    // Enable TOTP
    await this.userRepository.update(user.id, {
      totp_enabled: true,
    });

    return {
      success: true,
    };
  }

  async getProfile(userId: string) {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      select: ['id', 'email', 'username', 'totp_enabled', 'is_super_admin', 'created_at', 'last_login_at'],
    });

    if (!user) {
      throw new NotFoundException('User not found');
    }

    // Get user's license
    const license = await this.licenseRepository.findOne({
      where: { owner_user_id: user.id },
    });

    return {
      ...user,
      license_key: license?.license_key,
    };
  }

  async updateProfile(userId: string, dto: UpdateProfileDto) {
    const user = await this.userRepository.findOne({
      where: { id: userId },
    });

    if (!user) {
      throw new NotFoundException('User not found');
    }

    // Check if username is already taken
    if (dto.username && dto.username !== user.username) {
      const existingUser = await this.userRepository.findOne({
        where: { username: dto.username },
      });

      if (existingUser) {
        throw new ConflictException('Username already taken');
      }

      await this.userRepository.update(user.id, {
        username: dto.username,
      });
    }

    return this.getProfile(userId);
  }

  async forgotPassword(email: string) {
    // SMTP integration pending — returns 200 to avoid leaking account enumeration
    this.logger.warn(`Password reset requested for ${email} — SMTP not yet configured`);
    return {
      message: 'Password reset is not yet configured. Contact your administrator.',
    };
  }

  async resetPassword(token: string, _password: string) {
    this.logger.warn(`Password reset attempted with token ${token} — SMTP not yet configured`);
    throw new NotImplementedException('Password reset not yet configured');
  }

  // Helper methods

  private async generateTokens(user: User, licenseId?: string) {
    const payload: Record<string, unknown> = {
      sub: user.id,
      email: user.email,
      username: user.username,
      is_super_admin: user.is_super_admin,
    };

    if (licenseId) {
      payload.license_id = licenseId;
    }

    const accessToken = await this.jwtService.signAsync(payload, {
      secret: this.configService.get<string>('jwt.secret'),
      expiresIn: this.configService.get<number>('jwt.accessExpirySeconds') || 900,
    });

    const refreshToken = await this.jwtService.signAsync(payload, {
      secret: this.configService.get<string>('jwt.secret'),
      expiresIn: this.configService.get<number>('jwt.refreshExpirySeconds') || 604800,
    });

    return {
      access_token: accessToken,
      refresh_token: refreshToken,
    };
  }

  private async verifyTotpCode(user: User, code: string): Promise<boolean> {
    if (!user.totp_secret) {
      return false;
    }

    const totp = new OTPAuth.TOTP({
      issuer: 'Corrosion',
      label: user.email,
      algorithm: 'SHA1',
      digits: 6,
      period: 30,
      secret: OTPAuth.Secret.fromBase32(user.totp_secret),
    });

    const delta = totp.validate({ token: code, window: 1 });
    return delta !== null;
  }

  private generateLicenseKey(): string {
    const part1 = randomBytes(2).toString('hex').toUpperCase();
    const part2 = randomBytes(2).toString('hex').toUpperCase();
    const part3 = randomBytes(2).toString('hex').toUpperCase();
    return `CORR-${part1}-${part2}-${part3}`;
  }
}
