/**
 * ArtifactStore - Create, get, read artifacts per DD §11.1
 *
 * Implements ArtifactStore contract (contracts §14).
 * - create: write temp → sha256+size → atomic rename → ingest artifact.created
 * - artifact_id = art_<ulid>, uri per artifact-naming-v1
 * - INV-1: calls EventIngestor, does not UPDATE directly
 *
 * @module packages/runtime/src/artifacts/ArtifactStore
 */

import { mkdirSync, renameSync, writeFileSync, readFileSync, existsSync, statSync } from 'fs'
import { join, dirname, extname, basename } from 'path'
import { randomUUID } from 'crypto'
import { createHash } from 'crypto'

import type {
  ArtifactID,
  ArtifactRef,
  ArtifactCreateInput,
  ArtifactContext,
  ArtifactReadResult,
  ArtifactStore as IArtifactStore,
  SessionID,
  ISOTimeString,
  RuntimeEvent,
} from '@aircoding/contracts'

import { EventIngestor } from '../events/EventIngestor.js'

// Artifact type to directory mapping per artifact-naming-v1 §6
const ARTIFACT_TYPE_DIRS: Record<string, string> = {
  message_snapshot: 'messages',
  context_pack: 'context',
  stdout: 'command-runs',
  stderr: 'command-runs',
  combined_output: 'command-runs',
  tool_output: 'tool-runs',
  build_log: 'builds',
  test_report: 'tests',
  static_analysis_report: 'static-analysis',
  debug_report: 'debug',
  backtrace: 'debug',
  screenshot: 'screenshots',
  pcap: 'pcaps',
  core_dump: 'core-dumps',
  diff: 'diffs',
  review_report: 'reports',
  doctor_report: 'doctor',
  permission_report: 'permissions',
  workspace_diff: 'workspaces',
  ui_asset: 'ui-assets',
  log: 'logs',
  other: 'other',
}

const ARTIFACT_TYPE_EXTENSIONS: Record<string, string> = {
  message_snapshot: '.json.gz',
  context_pack: '.json.gz',
  stdout: '.txt.gz',
  stderr: '.txt.gz',
  combined_output: '.txt.gz',
  tool_output: '.json.gz',
  build_log: '.txt.gz',
  test_report: '.json',
  static_analysis_report: '.json',
  debug_report: '.md',
  backtrace: '.txt',
  screenshot: '.png',
  pcap: '.pcap',
  core_dump: '.core',
  diff: '.patch',
  review_report: '.md',
  doctor_report: '.json',
  permission_report: '.json',
  workspace_diff: '.patch',
  ui_asset: '.svg',
  log: '.log',
  other: '.bin',
}

/**
 * ArtifactStore implements the ArtifactStore contract per DD §11.1.
 */
export class ArtifactStore implements IArtifactStore {
  private artifactRoot: string
  private sessionId: SessionID
  private projectId: string
  private eventIngestor: EventIngestor

  constructor(
    artifactRoot: string,
    sessionId: SessionID,
    projectId: string,
    eventIngestor?: EventIngestor
  ) {
    this.artifactRoot = artifactRoot
    this.sessionId = sessionId
    this.projectId = projectId
    this.eventIngestor = eventIngestor ?? new EventIngestor()
  }

  async create(input: ArtifactCreateInput, context: ArtifactContext): Promise<ArtifactRef> {
    const artifactId = this.generateArtifactId()

    const content = input.content ?? ''
    const contentBuffer = Buffer.from(content, 'utf-8')

    const tempDir = join(this.artifactRoot, 'tmp')
    mkdirSync(tempDir, { recursive: true })
    const tempPath = join(tempDir, `${artifactId}.tmp`)
    writeFileSync(tempPath, contentBuffer)

    const sha256 = this.computeSha256(contentBuffer)
    const sizeBytes = contentBuffer.length

    const targetDir = this.getTargetDirectory(input.type, context)
    const targetPath = join(targetDir, this.generateFilename(input, artifactId))

    mkdirSync(dirname(targetPath), { recursive: true })
    renameSync(tempPath, targetPath)

    const uri = `artifact://project/${this.projectId}/session/${this.sessionId}/${artifactId}`
    const artifactRef: ArtifactRef = {
      artifact_id: artifactId,
      uri,
      path: targetPath,
      type: input.type,
      sha256,
      size_bytes: sizeBytes,
    }

    await this.ingestArtifactCreated(artifactRef, input, context)

    return artifactRef
  }

  async get(artifactId: ArtifactID): Promise<ArtifactRef | undefined> {
    const artifactPath = this.findArtifactPath(artifactId)
    if (!artifactPath) {
      return undefined
    }

    const stat = statSync(artifactPath)
    const content = readFileSync(artifactPath)
    const sha256 = this.computeSha256(content)

    return {
      artifact_id: artifactId,
      uri: `artifact://project/${this.projectId}/session/${this.sessionId}/${artifactId}`,
      path: artifactPath,
      type: this.inferArtifactType(artifactPath),
      sha256,
      size_bytes: stat.size,
    }
  }

  async read(artifactId: ArtifactID): Promise<ArtifactReadResult> {
    const artifact = await this.get(artifactId)
    if (!artifact) {
      throw new Error(`Artifact ${artifactId} not found`)
    }

    const content = readFileSync(artifact.path)
    const contentType = this.inferContentType(artifact.path)

    return {
      artifact,
      content,
      content_type: contentType,
    }
  }

  private generateArtifactId(): ArtifactID {
    return `art_${randomUUID().replace(/-/g, '').slice(0, 24)}` as ArtifactID
  }

  private getTargetDirectory(type: string, context: ArtifactContext): string {
    const dir = ARTIFACT_TYPE_DIRS[type] ?? 'other'

    if (dir === 'command-runs' && context.command_run_id) {
      return join(this.artifactRoot, 'command-runs', context.command_run_id)
    }
    if (dir === 'tool-runs' && context.tool_run_id) {
      return join(this.artifactRoot, 'tool-runs', context.tool_run_id)
    }
    if (dir === 'workspaces' && context.task_id) {
      return join(this.artifactRoot, 'workspaces', context.task_id)
    }

    return join(this.artifactRoot, dir)
  }

  private generateFilename(input: ArtifactCreateInput, artifactId: string): string {
    const timestamp = this.formatTimestamp(new Date())
    const slug = this.generateSlug(input.original_name ?? 'artifact')
    const ext = ARTIFACT_TYPE_EXTENSIONS[input.type] ?? '.bin'

    return `${timestamp}-${artifactId}-${slug}${ext}`
  }

  private formatTimestamp(date: Date): string {
    const iso = date.toISOString()
    return iso.replace(/[-:]/g, '').replace(/\.\d{3}/, '000')
  }

  private generateSlug(name: string): string {
    return name
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/^-+|-+$/g, '')
      .slice(0, 64)
  }

  private computeSha256(content: Buffer): string {
    return createHash('sha256').update(content).digest('hex')
  }

  private findArtifactPath(artifactId: ArtifactID): string | undefined {
    const search = (dir: string): string | undefined => {
      if (!existsSync(dir)) return undefined

      const entries = require('fs').readdirSync(dir)
      for (const entry of entries) {
        const fullPath = join(dir, entry)
        const stat = statSync(fullPath)
        if (stat.isDirectory()) {
          const found = search(fullPath)
          if (found) return found
        } else if (entry.includes(artifactId)) {
          return fullPath
        }
      }
      return undefined
    }

    return search(this.artifactRoot)
  }

  private inferArtifactType(filePath: string): string {
    const dir = basename(dirname(filePath))
    const typeMap: Record<string, string> = {
      logs: 'log',
      messages: 'message_snapshot',
      context: 'context_pack',
      'command-runs': 'stdout',
      'tool-runs': 'tool_output',
      builds: 'build_log',
      tests: 'test_report',
      'static-analysis': 'static_analysis_report',
      debug: 'debug_report',
      screenshots: 'screenshot',
      pcaps: 'pcap',
      'core-dumps': 'core_dump',
      diffs: 'diff',
      reports: 'review_report',
      doctor: 'doctor_report',
      permissions: 'permission_report',
      workspaces: 'workspace_diff',
      'ui-assets': 'ui_asset',
      other: 'other',
    }
    return typeMap[dir] ?? 'other'
  }

  private inferContentType(filePath: string): string {
    const ext = extname(filePath).toLowerCase()
    const contentTypes: Record<string, string> = {
      '.json': 'application/json',
      '.json.gz': 'application/json+gzip',
      '.txt': 'text/plain',
      '.txt.gz': 'text/plain+gzip',
      '.md': 'text/markdown',
      '.png': 'image/png',
      '.svg': 'image/svg+xml',
      '.pcap': 'application/vnd.tcpdump.pcap',
      '.patch': 'text/diff',
      '.log': 'text/plain',
      '.xml': 'application/xml',
      '.core': 'application/octet-stream',
      '.bin': 'application/octet-stream',
    }
    return contentTypes[ext] ?? 'application/octet-stream'
  }

  private async ingestArtifactCreated(
    artifactRef: ArtifactRef,
    input: ArtifactCreateInput,
    context: ArtifactContext
  ): Promise<void> {
    const now = new Date().toISOString() as ISOTimeString

    const payload = {
      artifact_id: artifactRef.artifact_id,
      type: artifactRef.type,
      uri: artifactRef.uri,
      path: artifactRef.path,
      original_name: input.original_name,
      size_bytes: artifactRef.size_bytes,
      sha256: artifactRef.sha256,
      task_id: context.task_id,
      agent_id: context.agent_id,
      tool_run_id: context.tool_run_id,
      command_run_id: context.command_run_id,
      associated_entity_type: input.associated_entity_type,
      associated_entity_id: input.associated_entity_id,
      metadata: input.metadata,
    }

    const event: RuntimeEvent<typeof payload> = {
      id: `evt_${randomUUID().replace(/-/g, '').slice(0, 24)}` as any,
      type: 'artifact.created',
      version: 1,
      timestamp: now,
      session_id: this.sessionId,
      project_id: this.projectId as any,
      source: {
        kind: 'system',
      },
      route: ['artifact', 'created'],
      payload,
    }

    await this.eventIngestor.ingest(event)
  }
}

export function createArtifactStore(
  artifactRoot: string,
  sessionId: SessionID,
  projectId: string,
  eventIngestor?: EventIngestor
): ArtifactStore {
  return new ArtifactStore(artifactRoot, sessionId, projectId, eventIngestor)
}