import {
    ConflictException,
    Inject,
    Injectable,
    Logger,
    NotFoundException,
  } from '@nestjs/common';
  import { DataSource } from 'typeorm';
  import * as argon2 from 'argon2';
  import { LANDLORD_DATA_SOURCE } from '../../database/database.module';
  import { TenantConnectionService } from '../../database/tenant-connection.service';
  import { Tenant } from '../../database/landlord/entities/tenant.entity';
  import { User } from '../../database/tenant/entities/user.entity';
  import { Role } from '../../database/tenant/entities/role.entity';
  import { Permission } from '../../database/tenant/entities/permission.entity';
  import { RolePermission } from '../../database/tenant/entities/role-permission.entity';
  import { UserRole } from '../../database/tenant/entities/user-role.entity';
  import { Office } from '../../database/tenant/entities/office.entity';
  import { CreateTenantDto } from './dto/create-tenant.dto';
  import { DEFAULT_PERMISSIONS } from '../../common/permissions';

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

    constructor(
      @Inject(LANDLORD_DATA_SOURCE) private readonly landlord: DataSource,
      private readonly tenantConn: TenantConnectionService,
    ) {}

    async list(): Promise<Tenant[]> {
      return this.landlord.getRepository(Tenant).find({
        order: { createdAt: 'DESC' },
      });
    }

    async findBySubdomain(subdomain: string): Promise<Tenant> {
      const t = await this.landlord
        .getRepository(Tenant)
        .findOne({ where: { subdomain: subdomain.toLowerCase() } });
      if (!t) throw new NotFoundException('Tenant not found');
      return t;
    }

    async create(dto: CreateTenantDto): Promise<{ tenant: Tenant; adminUserId: string }> {
      const repo = this.landlord.getRepository(Tenant);
      const subdomain = dto.subdomain.toLowerCase();

      const existing = await repo.findOne({ where: { subdomain } });
      if (existing) throw new ConflictException('Subdomain already taken');

      const tenant = repo.create({
        subdomain,
        name: dto.name,
        legalName: dto.legalName,
        contactPhone: dto.contactPhone,
        contactEmail: dto.adminEmail.toLowerCase(),
        status: 'active',
      });
      await repo.save(tenant);

      // Provision tenant DB and seed defaults
      const adminUserId = await this.provisionTenantSchema(subdomain, dto);

      this.logger.log(`Provisioned tenant "${subdomain}" (id=${tenant.id})`);
      return { tenant, adminUserId };
    }

    async provisionTenantSchema(
      subdomain: string,
      dto: CreateTenantDto,
    ): Promise<string> {
      const ds = await this.tenantConn.getTenantDataSource(subdomain);

      // Seed permissions
      const permRepo = ds.getRepository(Permission);
      const existingPerms = await permRepo.find();
      const existingKeys = new Set(existingPerms.map((p) => p.key));
      const toInsert = DEFAULT_PERMISSIONS.filter(
        (p) => !existingKeys.has(p.key),
      ).map((p) => permRepo.create(p));
      if (toInsert.length > 0) await permRepo.save(toInsert);

      const allPerms = await permRepo.find();

      // Seed Super Admin role with all permissions
      const roleRepo = ds.getRepository(Role);
      let adminRole = await roleRepo.findOne({ where: { name: 'Super Admin' } });
      if (!adminRole) {
        adminRole = await roleRepo.save(
          roleRepo.create({
            name: 'Super Admin',
            description: 'Full system access',
            isSystem: true,
          }),
        );
      }

      const rpRepo = ds.getRepository(RolePermission);
      const existingRp = await rpRepo.find({ where: { roleId: adminRole.id } });
      const existingPermIds = new Set(existingRp.map((rp) => rp.permissionId));
      const newRp = allPerms
        .filter((p) => !existingPermIds.has(p.id))
        .map((p) =>
          rpRepo.create({ roleId: adminRole!.id, permissionId: p.id }),
        );
      if (newRp.length > 0) await rpRepo.save(newRp);

      // Seed default Head Office
      const officeRepo = ds.getRepository(Office);
      let headOffice = await officeRepo.findOne({ where: { code: 'HEAD' } });
      if (!headOffice) {
        headOffice = await officeRepo.save(
          officeRepo.create({
            code: 'HEAD',
            name: dto.name + ' - Head Office',
            type: 'head',
            active: true,
          }),
        );
      }

      // Seed admin user
      const userRepo = ds.getRepository(User);
      const email = dto.adminEmail.toLowerCase();
      let admin = await userRepo.findOne({ where: { email } });
      if (!admin) {
        const passwordHash = await argon2.hash(dto.adminPassword);
        admin = await userRepo.save(
          userRepo.create({
            email,
            fullName: dto.adminFullName,
            passwordHash,
            active: true,
            isBypass: true,
            officeId: headOffice.id,
          }),
        );
      }

      const urRepo = ds.getRepository(UserRole);
      const existingUr = await urRepo.findOne({
        where: { userId: admin.id, roleId: adminRole.id },
      });
      if (!existingUr) {
        await urRepo.save(
          urRepo.create({ userId: admin.id, roleId: adminRole.id }),
        );
      }

      return admin.id;
    }
  }
  