import type { File } from '@vitest/runner'
import type { Logger } from '../logger'
import type { ResolvedConfig } from '../types/config'
import fs, { existsSync } from 'node:fs'
import { rm } from 'node:fs/promises'
import { dirname, relative, resolve } from 'pathe'
import { Vitest } from '../core'

export interface SuiteResultCache {
  failed: boolean
  duration: number
}

export class ResultsCache {
  private cache = new Map<string, SuiteResultCache>()
  private workspacesKeyMap = new Map<string, string[]>()
  private cachePath: string | null = null
  private version: string
  private root = '/'

  constructor(private logger: Logger) {
    this.version = Vitest.version
  }

  public getCachePath(): string | null {
    return this.cachePath
  }

  setConfig(root: string, config: ResolvedConfig['cache']): void {
    this.root = root
    if (config) {
      this.cachePath = resolve(config.dir, 'results.json')
    }
  }

  getResults(key: string): SuiteResultCache | undefined {
    return this.cache.get(key)
  }

  async clearCache(): Promise<void> {
    if (this.cachePath && existsSync(this.cachePath)) {
      await rm(this.cachePath, { force: true, recursive: true })
      this.logger.log('[cache] cleared results cache at', this.cachePath)
    }
  }

  async readFromCache(): Promise<void> {
    if (!this.cachePath) {
      return
    }

    if (!fs.existsSync(this.cachePath)) {
      return
    }

    const resultsCache = await fs.promises.readFile(this.cachePath, 'utf8')
    const { results, version } = JSON.parse(resultsCache || '[]')
    const [major, minor] = version.split('.')
    // handling changed in 0.30.0
    if (major > 0 || Number(minor) >= 30) {
      this.cache = new Map(results)
      this.version = version
      results.forEach(([spec]: [string]) => {
        const [projectName, relativePath] = spec.split(':')
        const keyMap = this.workspacesKeyMap.get(relativePath) || []
        keyMap.push(projectName)
        this.workspacesKeyMap.set(relativePath, keyMap)
      })
    }
  }

  updateResults(files: File[]): void {
    files.forEach((file) => {
      const result = file.result
      if (!result) {
        return
      }
      const duration = result.duration || 0
      // store as relative, so cache would be the same in CI and locally
      const relativePath = relative(this.root, file.filepath)
      this.cache.set(`${file.projectName || ''}:${relativePath}`, {
        duration: duration >= 0 ? duration : 0,
        failed: result.state === 'fail',
      })
    })
  }

  removeFromCache(filepath: string): void {
    this.cache.forEach((_, key) => {
      if (key.endsWith(filepath)) {
        this.cache.delete(key)
      }
    })
  }

  async writeToCache(): Promise<void> {
    if (!this.cachePath) {
      return
    }

    const results = Array.from(this.cache.entries())

    const cacheDirname = dirname(this.cachePath)

    if (!fs.existsSync(cacheDirname)) {
      await fs.promises.mkdir(cacheDirname, { recursive: true })
    }

    const cache = JSON.stringify({
      version: this.version,
      results,
    })

    await fs.promises.writeFile(this.cachePath, cache)
  }
}
