/// Integrity checks and diagnostic reports.

pub(all) struct VerificationReport {
  result : VerifyResult
  checked_chunks : Int
  checked_bytes : Int
  warnings : Array[String]
} derive(Show)

pub fn VerificationReport::result(self : VerificationReport) -> VerifyResult { self.result }
pub fn VerificationReport::passed(self : VerificationReport) -> Bool { self.result.ok() }
pub fn VerificationReport::checked(self : VerificationReport) -> Int { self.checked_chunks }
pub fn VerificationReport::bytes(self : VerificationReport) -> Int { self.checked_bytes }
pub fn VerificationReport::warnings(self : VerificationReport) -> Array[String] { self.warnings.copy() }

pub fn verify_store(store : ChunkStore, manifest : Manifest) -> VerificationReport {
  if !manifest.is_valid() || !manifest_validate_order(manifest) {
    return { result: InvalidManifest(reason="manifest metadata or root is invalid"), checked_chunks: 0, checked_bytes: 0, warnings: [] }
  }
  let mut checked = 0
  let mut bytes = 0
  let warnings = []
  for reference in manifest.references() {
    match store.get(reference.id()) {
      None => return { result: MissingChunk(digest=reference.id(), position=reference.at()), checked_chunks: checked, checked_bytes: bytes, warnings }
      Some(data) => {
        let actual = digest_bytes(data)
        if data.length() != reference.length() {
          return { result: SizeMismatch(expected=reference.length(), actual=data.length()), checked_chunks: checked, checked_bytes: bytes, warnings }
        }
        if !digest_equal(actual, reference.id()) {
          return { result: CorruptChunk(expected=reference.id(), actual=actual, position=reference.at()), checked_chunks: checked, checked_bytes: bytes, warnings }
        }
        checked = checked + 1
        bytes = bytes + data.length()
      }
    }
  }
  if bytes != manifest.size() {
    return { result: SizeMismatch(expected=manifest.size(), actual=bytes), checked_chunks: checked, checked_bytes: bytes, warnings }
  }
  { result: Valid, checked_chunks: checked, checked_bytes: bytes, warnings }
}

pub fn verify_bytes(data : Array[Byte], expected : String) -> Bool {
  digest_equal(digest_bytes(data), expected)
}

pub fn verify_manifest_root(manifest : Manifest) -> Bool {
  manifest.root_digest() == merkle_root(manifest_digests(manifest))
}

pub fn verify_proof_for_manifest(manifest : Manifest, position : Int) -> Bool {
  if position < 0 || position >= manifest.count() { return false }
  let digests = manifest_digests(manifest)
  let proof = merkle_proof(digests, position)
  verify_merkle_proof(digests[position], proof, manifest.root_digest())
}

pub fn report_message(report : VerificationReport) -> String {
  match report.result() {
    Valid => "valid: \{report.checked()} chunks, \{report.bytes()} bytes"
    InvalidManifest(reason~) => "invalid manifest: \{reason}"
    MissingChunk(digest~, position~) => "missing chunk \{position}: \{digest}"
    CorruptChunk(expected~, actual~, position~) => "corrupt chunk \{position}: expected \{expected}, got \{actual}"
    SizeMismatch(expected~, actual~) => "size mismatch: expected \{expected}, got \{actual}"
  }
}

pub fn collect_missing(store : ChunkStore, manifest : Manifest) -> Array[ChunkRef] {
  let missing = []
  for reference in manifest.references() {
    if !store.has(reference.id()) { missing.push(reference) }
  }
  missing
}

pub fn collect_extra(store : ChunkStore, manifest : Manifest) -> Array[String] {
  let extra = []
  for digest in store.all_digests() {
    if !manifest_contains(manifest, digest) { extra.push(digest) }
  }
  extra
}