﻿import {
  ConflictException,
  Injectable,
  Logger,
  NotFoundException,
  UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { InjectRepository } from "@nestjs/typeorm";
import * as bcrypt from "bcrypt";
import { Repository } from "typeorm";
import { CreateUserDto } from "./dto/create-user.dto";
import { LoginDto } from "./dto/login.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
import { UserRole, Usuario } from "./entities/usuario.entity";

const DEFAULT_PERMISSIONS_BY_ROLE: Record<
  string,
  Record<string, "none" | "read" | "write">
> = {
  [UserRole.SUPER_ADMIN]: {
    usuarios: "write",
    clientes: "write",
    vehiculos: "write",
    ots: "write",
    catalogos: "write",
    productos: "write",
    historial: "write",
    sync: "write",
    uploads: "write",
    cotizaciones: "write",
    reportes: "write",
  },
  [UserRole.ADMIN]: {
    usuarios: "read",
    clientes: "write",
    vehiculos: "write",
    ots: "write",
    catalogos: "write",
    productos: "write",
    historial: "read",
    sync: "write",
    uploads: "write",
    cotizaciones: "write",
    reportes: "write",
  },
  [UserRole.OPERATIVO]: {
    usuarios: "none",
    clientes: "read",
    vehiculos: "read",
    ots: "write",
    catalogos: "read",
    productos: "read",
    historial: "read",
    sync: "write",
    uploads: "write",
    cotizaciones: "write",
    reportes: "read",
  },
  [UserRole.RECEPCION]: {
    usuarios: "none",
    clientes: "read",
    vehiculos: "read",
    ots: "write",
    catalogos: "read",
    productos: "read",
    historial: "read",
    sync: "write",
    uploads: "write",
    cotizaciones: "write",
    reportes: "read",
  },
  [UserRole.MECANICO]: {
    usuarios: "none",
    clientes: "read",
    vehiculos: "read",
    ots: "write",
    catalogos: "read",
    productos: "read",
    historial: "read",
    sync: "write",
    uploads: "write",
    cotizaciones: "write",
    reportes: "none",
  },
};
@Injectable()
export class AuthService {
  private readonly logger = new Logger(AuthService.name);

  constructor(
    @InjectRepository(Usuario)
    private usuarioRepository: Repository<Usuario>,
    private jwtService: JwtService,
  ) {}

  private buildPermissions(
    usuario: Usuario,
  ): Record<string, "none" | "read" | "write"> {
    const base =
      DEFAULT_PERMISSIONS_BY_ROLE[usuario.rol] ||
      DEFAULT_PERMISSIONS_BY_ROLE[UserRole.OPERATIVO];
    return { ...base, ...(usuario.permisos || {}) };
  }

  async login(loginDto: LoginDto) {
    const { username, password } = loginDto;

    const usuario = await this.usuarioRepository.findOne({
      where: { username, activo: true },
    });

    if (!usuario) {
      throw new UnauthorizedException("Credenciales inválidas");
    }

    const isPasswordValid = await bcrypt.compare(
      password,
      usuario.passwordHash,
    );

    if (!isPasswordValid) {
      throw new UnauthorizedException("Credenciales inválidas");
    }

    await this.usuarioRepository.update(usuario.id, {
      ultimoLogin: new Date(),
    });

    const permissions = this.buildPermissions(usuario);

    const payload = {
      sub: usuario.id,
      username: usuario.username,
      rol: usuario.rol,
      permissions,
    };

    return {
      accessToken: this.jwtService.sign(payload),
      refreshToken: this.jwtService.sign(payload, { expiresIn: "30d" }),
      user: {
        id: usuario.id,
        username: usuario.username,
        nombre: usuario.nombre,
        rol: usuario.rol,
        permissions,
      },
    };
  }

  async refreshToken(refreshToken: string) {
    try {
      const payload = this.jwtService.verify(refreshToken);
      const usuario = await this.usuarioRepository.findOne({
        where: { id: payload.sub, activo: true },
      });

      if (!usuario) {
        throw new UnauthorizedException("Token inválido");
      }

      const permissions = this.buildPermissions(usuario);

      const newPayload = {
        sub: usuario.id,
        username: usuario.username,
        rol: usuario.rol,
        permissions,
      };

      return {
        accessToken: this.jwtService.sign(newPayload),
        refreshToken: this.jwtService.sign(newPayload, { expiresIn: "30d" }),
      };
    } catch {
      throw new UnauthorizedException("Token inválido o expirado");
    }
  }

  async getProfile(userId: number) {
    const usuario = await this.usuarioRepository.findOne({
      where: { id: userId },
      select: [
        "id",
        "username",
        "nombre",
        "email",
        "rol",
        "createdAt",
        "permisos",
      ],
    });

    if (!usuario) {
      throw new UnauthorizedException("Usuario no encontrado");
    }

    return {
      ...usuario,
      permissions: this.buildPermissions(usuario),
    };
  }

  async validateUser(userId: number): Promise<Usuario | null> {
    return this.usuarioRepository.findOne({
      where: { id: userId, activo: true },
    });
  }

  async listUsers() {
    const rows = await this.usuarioRepository.find({ order: { id: "ASC" } });
    return rows.map((u) => ({
      id: u.id,
      username: u.username,
      nombre: u.nombre,
      email: u.email,
      rol: u.rol,
      activo: u.activo,
      ultimoLogin: u.ultimoLogin,
      createdAt: u.createdAt,
      permissions: this.buildPermissions(u),
    }));
  }

  async createUser(dto: CreateUserDto) {
    const existing = await this.usuarioRepository.findOne({
      where: { username: dto.username },
    });
    if (existing) {
      throw new ConflictException(`El usuario ${dto.username} ya existe`);
    }

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

    const user = this.usuarioRepository.create({
      username: dto.username,
      nombre: dto.nombre,
      email: dto.email,
      rol: dto.rol,
      passwordHash: hashedPassword,
      permisos: dto.permisos,
      activo: true,
    });

    const saved = await this.usuarioRepository.save(user);
    return {
      id: saved.id,
      username: saved.username,
      nombre: saved.nombre,
      email: saved.email,
      rol: saved.rol,
      activo: saved.activo,
      permissions: this.buildPermissions(saved),
    };
  }

  async updateUser(id: number, dto: UpdateUserDto) {
    const user = await this.usuarioRepository.findOne({ where: { id } });
    if (!user) {
      throw new NotFoundException(`Usuario ${id} no encontrado`);
    }

    if (dto.username && dto.username !== user.username) {
      const exists = await this.usuarioRepository.findOne({
        where: { username: dto.username },
      });
      if (exists) {
        throw new ConflictException(`El usuario ${dto.username} ya existe`);
      }
      user.username = dto.username;
    }

    if (dto.password) {
      user.passwordHash = await bcrypt.hash(dto.password, 10);
    }

    if (dto.nombre !== undefined) user.nombre = dto.nombre;
    if (dto.email !== undefined) user.email = dto.email;
    if (dto.rol !== undefined) user.rol = dto.rol;
    if (dto.permisos !== undefined) user.permisos = dto.permisos;

    const saved = await this.usuarioRepository.save(user);
    return {
      id: saved.id,
      username: saved.username,
      nombre: saved.nombre,
      email: saved.email,
      rol: saved.rol,
      activo: saved.activo,
      permissions: this.buildPermissions(saved),
    };
  }

  async setUserActive(id: number, activo: boolean) {
    const user = await this.usuarioRepository.findOne({ where: { id } });
    if (!user) {
      throw new NotFoundException(`Usuario ${id} no encontrado`);
    }
    user.activo = activo;
    await this.usuarioRepository.save(user);
    return { ok: true };
  }

  async seedUsers() {
    const superAdminExists = await this.usuarioRepository.findOne({
      where: { username: "superadmin" },
    });
    if (!superAdminExists) {
      const hashedPassword = await bcrypt.hash("super123", 10);
      await this.usuarioRepository.save({
        username: "superadmin",
        nombre: "Super Administrador",
        passwordHash: hashedPassword,
        rol: UserRole.SUPER_ADMIN,
        permisos: DEFAULT_PERMISSIONS_BY_ROLE[UserRole.SUPER_ADMIN],
        activo: true,
      });
      this.logger.log("Super admin creado: superadmin / super123");
    }

    const adminExists = await this.usuarioRepository.findOne({
      where: { username: "admin" },
    });
    if (!adminExists) {
      const hashedPassword = await bcrypt.hash("admin123", 10);
      await this.usuarioRepository.save({
        username: "admin",
        nombre: "Administrador",
        passwordHash: hashedPassword,
        rol: UserRole.ADMIN,
        permisos: DEFAULT_PERMISSIONS_BY_ROLE[UserRole.ADMIN],
        activo: true,
      });
      this.logger.log("Admin creado: admin / admin123");
    }
  }

  async seedUsersIfTableExists(): Promise<void> {
    try {
      const [result] = await this.usuarioRepository.query(
        "SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'taller_usuarios'",
      );

      const tableExists = Number(result?.count ?? 0) > 0;

      if (!tableExists) {
        this.logger.warn(
          "taller_usuarios no existe. Ejecuta migraciones antes de iniciar sesión (ej. 'npm run migration:run').",
        );
        return;
      }

      await this.seedUsers();
    } catch (error: any) {
      this.logger.warn(
        `No se pudo validar/sembrar usuarios automáticamente: ${error?.message ?? error}`,
      );
    }
  }
}
