import { Injectable, Logger } from '@nestjs/common'
import { OcisClient } from '../integrations/ocis.client.js'
import { TenantsService } from './tenants.service.js'

const GIB = 1024 ** 3

// Human-facing plan labels, mirroring composables/useTenant.ts so the portal
// shows the same names everywhere.
const PLAN_LABEL: Record<string, string> = {
  mvp: 'Starter',
  pro: 'Business',
  enterprise: 'Enterprise',
}

// Fallback "allocated" per plan, used only when OCIS reports unlimited drives
// (quota.total === 0, the dev default). Once per-plan storage lands as real
// config (Price schema or tenant override), this map goes away.
const PLAN_QUOTA_BYTES: Record<string, number> = {
  mvp: 100 * GIB,
  pro: 1024 * GIB,
  enterprise: 5 * 1024 * GIB,
}

export interface StorageTopUser {
  name: string
  email: string
  usedBytes: number
}

export interface StorageSummary {
  // False when OCIS is unreachable / basic auth isn't configured — the page
  // renders an "unavailable" state rather than zeros that look like real data.
  available: boolean
  plan: string
  usedBytes: number
  quotaBytes: number
  freeBytes: number
  trashBytes: number
  driveCount: number
  topUsers: StorageTopUser[]
}

// Computes a tenant's aggregate storage live by joining its Dezky users to
// their OCIS personal drives. Read-only, no persistence — every call hits
// libregraph. Mapping key is email (OCIS users are auto-provisioned from
// Authentik's preferred_username and carry the same mail).
@Injectable()
export class StorageService {
  private readonly logger = new Logger(StorageService.name)

  constructor(
    private readonly tenants: TenantsService,
    private readonly ocis: OcisClient,
  ) {}

  async getStorageSummary(slug: string): Promise<StorageSummary> {
    const tenant = await this.tenants.findOneBySlug(slug)
    const planKey = tenant.plan ?? 'mvp'
    const planLabel = PLAN_LABEL[planKey] ?? planKey

    const empty: StorageSummary = {
      available: false,
      plan: planLabel,
      usedBytes: 0,
      quotaBytes: PLAN_QUOTA_BYTES[planKey] ?? 0,
      freeBytes: 0,
      trashBytes: 0,
      driveCount: 0,
      topUsers: [],
    }

    if (!this.ocis.configured) return empty

    const members = await this.tenants.listUsersForTenant(slug)
    const memberByEmail = new Map(
      members.map((m) => [m.email.toLowerCase(), m]),
    )

    let ocisUsers
    let personalDrives
    try {
      ;[ocisUsers, personalDrives] = await Promise.all([
        this.ocis.listUsers(),
        this.ocis.listDrives("driveType eq 'personal'"),
      ])
    } catch (err) {
      // OCIS down, wrong credentials, basic auth disabled — degrade gracefully.
      this.logger.warn(`Storage summary for "${slug}" falling back to unavailable: ${String(err)}`)
      return empty
    }

    // OCIS user id → email, so we can attribute each drive's owner to a member.
    const emailByOcisId = new Map(
      ocisUsers
        .filter((u) => u.mail)
        .map((u) => [u.id, u.mail!.toLowerCase()]),
    )

    let usedBytes = 0
    let trashBytes = 0
    let quotaFromDrives = 0
    let driveCount = 0
    const usedByEmail = new Map<string, number>()

    for (const drive of personalDrives) {
      const ownerId = drive.owner?.user?.id
      const email = ownerId ? emailByOcisId.get(ownerId) : undefined
      if (!email || !memberByEmail.has(email)) continue // not a member of this tenant

      const used = drive.quota?.used ?? 0
      usedBytes += used
      trashBytes += drive.quota?.deleted ?? 0
      quotaFromDrives += drive.quota?.total ?? 0
      driveCount += 1
      usedByEmail.set(email, (usedByEmail.get(email) ?? 0) + used)
    }

    // Prefer real OCIS-reported allocation; fall back to the plan map when
    // drives are unlimited (total 0).
    const quotaBytes = quotaFromDrives > 0 ? quotaFromDrives : (PLAN_QUOTA_BYTES[planKey] ?? 0)

    const topUsers: StorageTopUser[] = [...usedByEmail.entries()]
      .map(([email, used]) => ({
        email,
        name: memberByEmail.get(email)?.name ?? email,
        usedBytes: used,
      }))
      .sort((a, b) => b.usedBytes - a.usedBytes)
      .slice(0, 5)

    return {
      available: true,
      plan: planLabel,
      usedBytes,
      quotaBytes,
      freeBytes: Math.max(0, quotaBytes - usedBytes),
      trashBytes,
      driveCount,
      topUsers,
    }
  }
}
