import { Repository, IsNull } from 'typeorm';
import { Permission } from '../entities';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreatePermissionDto {
  code: string;
  name: string;
  description?: string;
  module?: string;
  action?: string;
  isActive?: boolean;
}

export interface UpdatePermissionDto {
  code?: string;
  name?: string;
  description?: string;
  module?: string;
  action?: string;
  isActive?: boolean;
}

export interface PermissionFilters {
  code?: string;
  module?: string;
  action?: string;
  isActive?: boolean;
  search?: string;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export class PermissionService {
  constructor(private readonly permissionRepository: Repository<Permission>) {}

  async create(ctx: ServiceContext, data: CreatePermissionDto): Promise<Permission> {
    const existing = await this.permissionRepository.findOne({
      where: { tenantId: ctx.tenantId, code: data.code, deletedAt: IsNull() },
    });

    if (existing) {
      throw new Error(`Permission with code '${data.code}' already exists`);
    }

    const permission = this.permissionRepository.create({
      tenantId: ctx.tenantId,
      code: data.code,
      name: data.name,
      description: data.description,
      module: data.module,
      action: data.action,
      isActive: data.isActive ?? true,
      createdBy: ctx.userId ?? null,
    });

    return this.permissionRepository.save(permission);
  }

  async findById(ctx: ServiceContext, id: string): Promise<Permission | null> {
    return this.permissionRepository.findOne({
      where: { id, tenantId: ctx.tenantId, deletedAt: IsNull() },
    });
  }

  async findByCode(ctx: ServiceContext, code: string): Promise<Permission | null> {
    return this.permissionRepository.findOne({
      where: { tenantId: ctx.tenantId, code, deletedAt: IsNull() },
    });
  }

  async findAll(ctx: ServiceContext): Promise<Permission[]> {
    return this.permissionRepository.find({
      where: { tenantId: ctx.tenantId, deletedAt: IsNull() },
      order: { module: 'ASC', name: 'ASC' },
    });
  }

  async findByModule(ctx: ServiceContext, module: string): Promise<Permission[]> {
    return this.permissionRepository.find({
      where: { tenantId: ctx.tenantId, module, deletedAt: IsNull() },
      order: { action: 'ASC' },
    });
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: PermissionFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<Permission>> {
    const qb = this.permissionRepository
      .createQueryBuilder('permission')
      .where('permission.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('permission.deleted_at IS NULL');

    if (filters.code) {
      qb.andWhere('permission.code = :code', { code: filters.code });
    }

    if (filters.module) {
      qb.andWhere('permission.module = :module', { module: filters.module });
    }

    if (filters.action) {
      qb.andWhere('permission.action = :action', { action: filters.action });
    }

    if (filters.isActive !== undefined) {
      qb.andWhere('permission.is_active = :isActive', { isActive: filters.isActive });
    }

    if (filters.search) {
      qb.andWhere('(permission.name ILIKE :search OR permission.code ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    const [data, total] = await qb
      .orderBy('permission.module', 'ASC')
      .addOrderBy('permission.name', 'ASC')
      .skip((page - 1) * limit)
      .take(limit)
      .getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async update(ctx: ServiceContext, id: string, data: UpdatePermissionDto): Promise<Permission | null> {
    const permission = await this.findById(ctx, id);
    if (!permission) return null;

    if (data.code && data.code !== permission.code) {
      const existing = await this.permissionRepository.findOne({
        where: { tenantId: ctx.tenantId, code: data.code, deletedAt: IsNull() },
      });
      if (existing) {
        throw new Error(`Permission with code '${data.code}' already exists`);
      }
    }

    Object.assign(permission, {
      ...data,
      updatedBy: ctx.userId ?? null,
    });

    return this.permissionRepository.save(permission);
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const permission = await this.findById(ctx, id);
    if (!permission) return false;

    permission.deletedAt = new Date();
    permission.deletedBy = ctx.userId ?? null;
    await this.permissionRepository.save(permission);

    return true;
  }

  async getModules(ctx: ServiceContext): Promise<string[]> {
    const result = await this.permissionRepository
      .createQueryBuilder('permission')
      .select('DISTINCT permission.module', 'module')
      .where('permission.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('permission.deleted_at IS NULL')
      .andWhere('permission.module IS NOT NULL')
      .orderBy('permission.module', 'ASC')
      .getRawMany();

    return result.map(r => r.module);
  }

  async checkPermission(ctx: ServiceContext, code: string): Promise<boolean> {
    const permission = await this.findByCode(ctx, code);
    return permission !== null && permission.isActive;
  }

  async bulkCreate(ctx: ServiceContext, permissions: CreatePermissionDto[]): Promise<Permission[]> {
    const created: Permission[] = [];

    for (const data of permissions) {
      try {
        const permission = await this.create(ctx, data);
        created.push(permission);
      } catch {
        // Skip duplicates
      }
    }

    return created;
  }
}
