import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { Admin } from '../entities/admin.entity';
import { Role } from '../../roles/entities/role.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { wrap } from '@mikro-orm/core';
import { EntityManager } from '@mikro-orm/postgresql';
import { CreateMyRestaurantAdminDto } from '../dto/create-my-restaurant-admin.dto';
import { UpdateAdminDto } from '../dto/update-admin.dto';
import { AdminRole } from '../entities/adminRole.entity';
import { normalizePhone } from '../../utils/phone.util';
import { AdminRepository } from '../repositories/admin.repository';
import { AdminRoleRepository } from '../repositories/admin-role.repository';

@Injectable()
export class AdminService {
  constructor(
    private readonly adminRepository: AdminRepository,
    private readonly adminRoleRepository: AdminRoleRepository,
    private readonly em: EntityManager,
  ) { }

  async findByPhone(phone: string) {
    const normalizedPhone = normalizePhone(phone);
    return this.adminRepository.findOne({ phone: normalizedPhone });
  }

  async finAdminById(
    adminId: string,
    restId: string,
  ) {
    const admin = await this.findOneByIdOrFail(adminId)
    const adminPlain = wrap(admin).toObject();
    const adminRole = await this.adminRoleRepository.findOne({ admin, restaurant: { id: restId } });

    return { ...adminPlain, role: adminRole?.role };
  }

  async findAllByRestaurantId(restId: string) {
    return this.adminRepository.find({ roles: { restaurant: { id: restId } } },
      { populate: ['roles', 'roles.role'], populateFilter: { roles: { restaurant: { id: restId } } } });
  }

  async createAdminForMyRestaurant(restId: string, dto: CreateMyRestaurantAdminDto) {
    const { phone, firstName, lastName, roleId } = dto;
    const normalizedPhone = normalizePhone(phone);
    let admin: Admin | null = null;
    admin = await this.adminRepository.findOne({
      phone: normalizedPhone,
    });
    if (!admin) {
      admin = this.adminRepository.create({
        phone: normalizedPhone,
        firstName,
        lastName,
      });
      await this.em.persistAndFlush(admin);
    }
    const role = await this.em.findOne(Role, { id: roleId, isSystem: false });
    if (!role) throw new NotFoundException('Role not found');

    const restaurant = await this.em.findOne(Restaurant, { id: restId });
    if (!restaurant) throw new NotFoundException('Restaurant not found');

    let adminRole = await this.adminRoleRepository.findOne({
      admin: admin,
      restaurant: restaurant,
    });

    if (!adminRole) {
      adminRole = this.adminRoleRepository.create({
        admin: admin,
        role,
        restaurant,
      });
    } else {
      adminRole.role = role;
    }

    await this.em.persistAndFlush(adminRole);
    return admin;
  }

  async createAdminForRestaurantBySuperAdmin(restId: string, dto: CreateMyRestaurantAdminDto) {
    const { phone, firstName, lastName, roleId } = dto;
    const normalizedPhone = normalizePhone(phone);
    let admin: Admin | null = null;
    admin = await this.adminRepository.findOne({
      phone: normalizedPhone,
    });
    if (!admin) {
      admin = this.adminRepository.create({
        phone: normalizedPhone,
        firstName,
        lastName,
      });
      await this.em.persistAndFlush(admin);
    }
    const role = await this.em.findOne(Role, { id: roleId });
    if (!role) throw new NotFoundException('Role* not found' + roleId);

    const restaurant = await this.em.findOne(Restaurant, { id: restId });
    if (!restaurant) throw new NotFoundException('Restaurant not found');

    let adminRole = await this.adminRoleRepository.findOne({
      admin: admin,
      restaurant: restaurant,
    });

    if (!adminRole) {
      adminRole = this.adminRoleRepository.create({
        admin: admin,
        role,
        restaurant,
      });
    } else {
      adminRole.role = role;
    }

    await this.em.persistAndFlush(adminRole);
    return admin;
  }

  async update(adminId: string, restId: string, dto: UpdateAdminDto) {
    const admin = await this.findOneByIdOrFail(adminId)

    const { roleId, ...rest } = dto;

    // Update scalar fields (firstName, lastName, phone)
    if (rest.firstName !== undefined) {
      admin.firstName = rest.firstName;
    }
    if (rest.lastName !== undefined) {
      admin.lastName = rest.lastName;
    }
    if (rest.phone !== undefined && rest.phone !== admin.phone) {
      const normalizedPhone = normalizePhone(rest.phone);
      const exists = await this.adminRepository.findOne({ phone: normalizedPhone });
      if (exists) {
        throw new ConflictException('This Phone Number is already taken');
      }
      admin.phone = normalizedPhone;
    }

    // Update role if roleId is provided
    if (roleId) {
      const role = await this.em.findOne(Role, { id: roleId, isSystem: false });
      if (!role) {
        throw new NotFoundException('Role not found');
      }

      // Find existing AdminRole for this admin and restaurant
      const existingAdminRole = await this.em.findOne(AdminRole, {
        admin: { id: adminId },
        restaurant: { id: restId },
      });

      if (existingAdminRole) {
        // Update existing role
        existingAdminRole.role = role;
      } else {
        const restaurant = await this.em.findOne(Restaurant, { id: restId });
        if (!restaurant) throw new NotFoundException('Restaurant not found');
        // Create new AdminRole
        const newAdminRole = this.em.create(AdminRole, {
          admin,
          role,
          restaurant,
        });
        admin.roles.add(newAdminRole);
      }
    }

    await this.em.persistAndFlush(admin);
    return admin;
  }

  async remove(adminId: string, restId: string): Promise<void> {
    const adminRole = await this.adminRoleRepository.findOne({ admin: { id: adminId }, restaurant: { id: restId } });
    if (!adminRole) {
      throw new NotFoundException('Admin role not found');
    }
    return this.em.removeAndFlush(adminRole);
  }

  async findOneByIdOrFail(adminId: string) {
    const admin = await this.adminRepository.findOne(
      { id: adminId },
      { populate: ['roles', 'roles.role', 'roles.restaurant'] },
    );

    if (!admin) {
      throw new NotFoundException('Admin not found');
    }
    return admin
  }
}
