/**
 * Auth Service
 * Mecánicas Diesel - ERP Suite
 */

import { Repository, DataSource } from 'typeorm';
import bcrypt from 'bcryptjs';
import { v4 as uuidv4 } from 'uuid';
import { User, UserRole } from './entities/user.entity';
import { RefreshToken } from './entities/refresh-token.entity';
import { Workshop } from './entities/workshop.entity';
import { LoginDto, RegisterDto, ChangePasswordDto } from './auth.dto';
import { generateAccessToken, generateRefreshToken as generateRefreshJwt, verifyToken } from '../../shared/utils/jwt.utils';

const REFRESH_TOKEN_EXPIRES_DAYS = 30;

export interface AuthResponse {
  user: Omit<User, 'passwordHash'>;
  token: string;
  refreshToken: string;
}

export class AuthService {
  private userRepository: Repository<User>;
  private refreshTokenRepository: Repository<RefreshToken>;
  private workshopRepository: Repository<Workshop>;

  constructor(dataSource: DataSource) {
    this.userRepository = dataSource.getRepository(User);
    this.refreshTokenRepository = dataSource.getRepository(RefreshToken);
    this.workshopRepository = dataSource.getRepository(Workshop);
  }

  /**
   * Login user
   */
  async login(dto: LoginDto): Promise<AuthResponse> {
    const user = await this.userRepository.findOne({
      where: { email: dto.email },
    });

    if (!user) {
      throw new Error('Credenciales inválidas');
    }

    if (!user.isActive) {
      throw new Error('Usuario inactivo');
    }

    const isValidPassword = await bcrypt.compare(dto.password, user.passwordHash);
    if (!isValidPassword) {
      throw new Error('Credenciales inválidas');
    }

    user.lastLoginAt = new Date();
    await this.userRepository.save(user);

    const tokenPayload = {
      userId: user.id,
      email: user.email,
      tenantId: user.tenantId,
      role: user.role,
    };

    const token = generateAccessToken(tokenPayload);
    const refreshToken = await this.createRefreshToken(user.id);

    return {
      user: user.toJSON() as Omit<User, 'passwordHash'>,
      token,
      refreshToken: refreshToken.token,
    };
  }

  /**
   * Register new user
   */
  async register(dto: RegisterDto, tenantId?: string): Promise<AuthResponse> {
    const existingUser = await this.userRepository.findOne({
      where: { email: dto.email },
    });

    if (existingUser) {
      throw new Error('El email ya está registrado');
    }

    let finalTenantId = tenantId;
    let userRole = UserRole.MECANICO;

    if (dto.taller_name) {
      const workshop = this.workshopRepository.create({
        name: dto.taller_name,
        email: dto.email,
        isActive: true,
      });

      const savedWorkshop = await this.workshopRepository.save(workshop);
      finalTenantId = savedWorkshop.id;
      userRole = UserRole.ADMIN;
    } else if (!tenantId) {
      throw new Error('tenant_id es requerido cuando no se proporciona taller_name');
    }

    const passwordHash = await bcrypt.hash(dto.password, 10);

    const user = this.userRepository.create({
      email: dto.email,
      passwordHash,
      fullName: dto.full_name,
      tenantId: finalTenantId!,
      role: userRole,
      isActive: true,
      emailVerified: false,
    });

    const savedUser = await this.userRepository.save(user);

    const tokenPayload = {
      userId: savedUser.id,
      email: savedUser.email,
      tenantId: savedUser.tenantId,
      role: savedUser.role,
    };

    const token = generateAccessToken(tokenPayload);
    const refreshToken = await this.createRefreshToken(savedUser.id);

    return {
      user: savedUser.toJSON() as Omit<User, 'passwordHash'>,
      token,
      refreshToken: refreshToken.token,
    };
  }

  /**
   * Logout user (revoke refresh token)
   */
  async logout(refreshTokenString: string): Promise<void> {
    const refreshToken = await this.refreshTokenRepository.findOne({
      where: { token: refreshTokenString },
    });

    if (refreshToken && !refreshToken.revokedAt) {
      refreshToken.revokedAt = new Date();
      await this.refreshTokenRepository.save(refreshToken);
    }
  }

  /**
   * Refresh access token
   */
  async refresh(refreshTokenString: string): Promise<{ token: string }> {
    const refreshToken = await this.refreshTokenRepository.findOne({
      where: { token: refreshTokenString },
      relations: ['user'],
    });

    if (!refreshToken) {
      throw new Error('Refresh token inválido');
    }

    if (refreshToken.revokedAt) {
      throw new Error('Refresh token revocado');
    }

    if (new Date() > refreshToken.expiresAt) {
      throw new Error('Refresh token expirado');
    }

    if (!refreshToken.user.isActive) {
      throw new Error('Usuario inactivo');
    }

    const tokenPayload = {
      userId: refreshToken.user.id,
      email: refreshToken.user.email,
      tenantId: refreshToken.user.tenantId,
      role: refreshToken.user.role,
    };

    return { token: generateAccessToken(tokenPayload) };
  }

  /**
   * Get user profile
   */
  async getProfile(userId: string): Promise<Omit<User, 'passwordHash'>> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
    });

    if (!user) {
      throw new Error('Usuario no encontrado');
    }

    return user.toJSON() as Omit<User, 'passwordHash'>;
  }

  /**
   * Change password
   */
  async changePassword(userId: string, dto: ChangePasswordDto): Promise<void> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
    });

    if (!user) {
      throw new Error('Usuario no encontrado');
    }

    const isValidPassword = await bcrypt.compare(dto.currentPassword, user.passwordHash);
    if (!isValidPassword) {
      throw new Error('Contraseña actual incorrecta');
    }

    user.passwordHash = await bcrypt.hash(dto.newPassword, 10);
    await this.userRepository.save(user);
  }

  /**
   * Create refresh token
   */
  private async createRefreshToken(userId: string): Promise<RefreshToken> {
    const token = uuidv4() + '-' + uuidv4();
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + REFRESH_TOKEN_EXPIRES_DAYS);

    const refreshToken = this.refreshTokenRepository.create({
      userId,
      token,
      expiresAt,
    });

    return await this.refreshTokenRepository.save(refreshToken);
  }
}
