import { application } from '@application'
import { loggerService } from '@logger'
import { app } from 'electron'
import fs from 'fs'

const logger = loggerService.withContext('VersionService')

type OS = 'win' | 'mac' | 'linux' | 'unknown'
type Environment = 'prod' | 'dev'
type Packaged = 'packaged' | 'unpackaged'
type Mode = 'install' | 'portable'

/**
 * Version record stored in version.log
 */
interface VersionRecord {
  version: string
  os: OS
  environment: Environment
  packaged: Packaged
  mode: Mode
  timestamp: string
}

/**
 * Service for tracking application version history
 * Stores version information in userData/version.log for data migration and diagnostics
 */
class VersionService {
  private versionLogPath: string | null = null

  /**
   * Gets the full path to version.log file
   * @returns {string} Full path to version log file
   */
  private getVersionLogPath(): string {
    if (!this.versionLogPath) {
      this.versionLogPath = application.getPath('feature.version_log.file')
    }
    return this.versionLogPath
  }

  /**
   * Gets current operating system identifier
   * @returns {OS} OS identifier
   */
  private getCurrentOS(): OS {
    switch (process.platform) {
      case 'win32':
        return 'win'
      case 'darwin':
        return 'mac'
      case 'linux':
        return 'linux'
      default:
        return 'unknown'
    }
  }

  /**
   * Gets current environment (production or development)
   * @returns {Environment} Environment identifier
   */
  private getCurrentEnvironment(): Environment {
    return import.meta.env.MODE === 'production' ? 'prod' : 'dev'
  }

  /**
   * Gets packaging status
   * @returns {Packaged} Packaging status
   */
  private getPackagedStatus(): Packaged {
    return app.isPackaged ? 'packaged' : 'unpackaged'
  }

  /**
   * Gets installation mode (install or portable)
   * @returns {Mode} Installation mode
   */
  private getInstallMode(): Mode {
    return process.env.PORTABLE_EXECUTABLE_DIR !== undefined ? 'portable' : 'install'
  }

  /**
   * Generates version log line for current application state
   * @returns {string} Pipe-separated version record line
   */
  private generateCurrentVersionLine(): string {
    const version = app.getVersion()
    const os = this.getCurrentOS()
    const environment = this.getCurrentEnvironment()
    const packaged = this.getPackagedStatus()
    const mode = this.getInstallMode()
    const timestamp = new Date().toISOString()

    return `${version}|${os}|${environment}|${packaged}|${mode}|${timestamp}`
  }

  /**
   * Parses a version log line into a VersionRecord object
   * @param {string} line - Pipe-separated version record line
   * @returns {VersionRecord | null} Parsed version record or null if invalid
   */
  private parseVersionLine(line: string): VersionRecord | null {
    try {
      const parts = line.trim().split('|')
      if (parts.length !== 6) {
        return null
      }

      const [version, os, environment, packaged, mode, timestamp] = parts

      // Validate data
      if (
        !version ||
        !['win', 'mac', 'linux', 'unknown'].includes(os) ||
        !['prod', 'dev'].includes(environment) ||
        !['packaged', 'unpackaged'].includes(packaged) ||
        !['install', 'portable'].includes(mode) ||
        !timestamp
      ) {
        return null
      }

      return {
        version,
        os: os as OS,
        environment: environment as Environment,
        packaged: packaged as Packaged,
        mode: mode as Mode,
        timestamp
      }
    } catch (error) {
      logger.warn(`Failed to parse version line: ${line}`, error as Error)
      return null
    }
  }

  /**
   * Reads the last 1KB from version.log and returns all lines
   * Uses reverse reading from file end to avoid reading the entire file
   * @returns {string[]} Array of version lines from the last 1KB
   */
  private readLastVersionLines(): string[] {
    const logPath = this.getVersionLogPath()

    try {
      if (!fs.existsSync(logPath)) {
        return []
      }

      const stats = fs.statSync(logPath)
      const fileSize = stats.size

      if (fileSize === 0) {
        return []
      }

      // Read from the end of the file, 1KB is enough to find previous version
      // Typical line: "1.7.0-beta.3|win|prod|packaged|install|2025-01-15T08:30:00.000Z\n" (~70 bytes)
      // 1KB can store ~14 lines, which is more than enough
      const bufferSize = Math.min(1024, fileSize)
      const buffer = Buffer.alloc(bufferSize)

      const fd = fs.openSync(logPath, 'r')
      try {
        const startPosition = Math.max(0, fileSize - bufferSize)
        fs.readSync(fd, buffer, 0, bufferSize, startPosition)

        const content = buffer.toString('utf-8')
        const lines = content
          .trim()
          .split('\n')
          .filter((line) => line.trim())

        return lines
      } finally {
        fs.closeSync(fd)
      }
    } catch (error) {
      logger.error('Failed to read version log:', error as Error)
      return []
    }
  }

  /**
   * Appends a version record line to version.log
   * @param {string} line - Version record line to append
   */
  private appendVersionLine(line: string): void {
    const logPath = this.getVersionLogPath()

    try {
      fs.appendFileSync(logPath, line + '\n', 'utf-8')
      logger.debug(`Version recorded: ${line}`)
    } catch (error) {
      logger.error('Failed to append version log:', error as Error)
    }
  }

  /**
   * Records the current version on application startup
   * Only adds a new record if the version has changed since the last run
   */
  recordCurrentVersion(): void {
    try {
      const currentLine = this.generateCurrentVersionLine()
      const lines = this.readLastVersionLines()

      // Add new record if this is the first run or version has changed
      if (lines.length === 0) {
        logger.info('First run detected, creating version log')
        this.appendVersionLine(currentLine)
        return
      }

      const lastLine = lines[lines.length - 1]
      const lastRecord = this.parseVersionLine(lastLine)
      const currentVersion = app.getVersion()

      // Check if any meaningful field has changed (version, os, environment, packaged, mode)
      const currentOS = this.getCurrentOS()
      const currentEnvironment = this.getCurrentEnvironment()
      const currentPackaged = this.getPackagedStatus()
      const currentMode = this.getInstallMode()

      const hasMeaningfulChange =
        !lastRecord ||
        lastRecord.version !== currentVersion ||
        lastRecord.os !== currentOS ||
        lastRecord.environment !== currentEnvironment ||
        lastRecord.packaged !== currentPackaged ||
        lastRecord.mode !== currentMode

      if (hasMeaningfulChange) {
        logger.info(`Version information changed, recording new entry`)
        this.appendVersionLine(currentLine)
      } else {
        logger.debug(`Version information not changed, skip recording`)
      }
    } catch (error) {
      logger.error('Failed to record current version:', error as Error)
    }
  }

  /**
   * Gets the previous version record (last record with different version than current)
   * Reads from the last 1KB of version.log to find the most recent different version
   * Useful for detecting version upgrades and running migrations
   * @returns {VersionRecord | null} Previous version record or null if not available
   */
  getPreviousVersion(): VersionRecord | null {
    try {
      const lines = this.readLastVersionLines()
      if (lines.length === 0) {
        return null
      }

      const currentVersion = app.getVersion()

      // Read from the end backwards to find the first different version
      for (let i = lines.length - 1; i >= 0; i--) {
        const record = this.parseVersionLine(lines[i])
        if (record && record.version !== currentVersion) {
          return record
        }
      }

      return null
    } catch (error) {
      logger.error('Failed to get previous version:', error as Error)
      return null
    }
  }
}

/**
 * Singleton instance of VersionService
 */
export const versionService = new VersionService()
