# frozen_string_literal: true

require "digest"
require "fileutils"
require "json"
require "net/http"
require "open3"
require "securerandom"
require "time"
require "tmpdir"
require "yaml"

NOHUP_BIN = "/usr/bin/nohup"
PROFILES = {
  "dev" => {
    "environment" => "dev",
    "config_path" => "/Users/toki/agent-work/iop-dev/build/dev-runtime/edge.yaml",
    "config_arg" => "build/dev-runtime/edge.yaml",
    "edge_bin" => "/Users/toki/agent-work/iop-dev/build/dev-runtime/bin/edge",
    "runtime_root" => "/Users/toki/agent-work/iop-dev",
    "admin_addr" => "127.0.0.1:19093",
    "health_url" => "http://127.0.0.1:18083/healthz",
    "listener_port" => 18_083,
    "log_path" => "/Users/toki/agent-work/iop-dev/build/dev-runtime/logs/iop-edge-token-issue.log",
    "lock_path" => "/Users/toki/agent-work/iop-dev/build/dev-runtime/.token-issue.lock",
    "api_base_url_runner" => "http://127.0.0.1:18083/v1",
    "metrics_url_runner" => "http://127.0.0.1:19101/metrics",
    "smoke_model" => "laguna-s:2.1"
  },
  "dev-corp" => {
    "environment" => "dev-corp",
    "config_path" => "/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/edge.yaml",
    "config_arg" => "/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/edge.yaml",
    "edge_bin" => "/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/bin/iop-edge",
    "runtime_root" => "/Users/toki/agent-work/iop-dev-corp",
    "admin_addr" => "127.0.0.1:19094",
    "health_url" => "http://127.0.0.1:18086/healthz",
    "listener_port" => 18_086,
    "log_path" => "/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/logs/iop-edge-token-issue.log",
    "lock_path" => "/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/.token-issue.lock",
    "api_base_url_runner" => "http://127.0.0.1:18086/v1",
    "metrics_url_runner" => "http://127.0.0.1:19105/metrics",
    "smoke_model" => "ornith:35b"
  }
}.freeze

def profile
  $profile || fail_safe("environment_missing")
end

def config_path
  profile.fetch("config_path")
end

def config_arg
  profile.fetch("config_arg")
end

def edge_bin
  profile.fetch("edge_bin")
end

def runtime_root
  profile.fetch("runtime_root")
end

def admin_addr
  profile.fetch("admin_addr")
end

def health_uri
  URI(profile.fetch("health_url"))
end

def listener_port
  Integer(profile.fetch("listener_port"))
end

def log_path
  profile.fetch("log_path")
end

def lock_path
  profile.fetch("lock_path")
end

def expected_command
  "#{edge_bin} --config #{config_arg} serve"
end

class SafeFailure < StandardError
  attr_reader :code

  def initialize(code)
    @code = code
    super(code)
  end
end

def fail_safe(code)
  raise SafeFailure, code
end

def with_transaction_lock
  File.open(lock_path, File::RDWR | File::CREAT, 0o600) do |lock|
    File.chmod(0o600, lock_path)
    fail_safe("transaction_busy") unless lock.flock(File::LOCK_EX | File::LOCK_NB)
    begin
      yield
    ensure
      lock.flock(File::LOCK_UN)
    end
  end
end

def read_payload
  payload = JSON.parse($stdin.read)
  fail_safe("invalid_action") unless %w[inspect candidate-check apply rollback api-smoke metrics selftest].include?(payload["action"])
  environment = payload["environment"]
  fail_safe("environment_invalid") unless environment.is_a?(String) && PROFILES.key?(environment)
  payload
rescue JSON::ParserError
  fail_safe("invalid_json")
end

def load_config(path = config_path)
  value = YAML.safe_load(File.read(path), aliases: true)
  fail_safe("invalid_config_root") unless value.is_a?(Hash)
  value
rescue Errno::ENOENT
  fail_safe("config_missing")
rescue Psych::Exception
  fail_safe("config_yaml_invalid")
end

def mappings(config)
  items = config.dig("openai", "principal_tokens") || []
  fail_safe("principal_tokens_invalid") unless items.is_a?(Array) && items.all? { |item| item.is_a?(Hash) }
  items
end

def validate_mappings!(items)
  refs = items.map { |item| item["token_ref"].to_s }
  hashes = items.map { |item| item["token_hash_sha256"].to_s.downcase }
  fail_safe("mapping_token_ref_invalid") if refs.any?(&:empty?) || refs.uniq.length != refs.length
  fail_safe("mapping_hash_invalid") if hashes.any? { |value| !value.match?(/\A[0-9a-f]{64}\z/) } || hashes.uniq.length != hashes.length
  fail_safe("mapping_principal_invalid") if items.any? { |item| item["principal_ref"].to_s.empty? }
end

def json_scalar(value)
  JSON.generate(value.to_s)
end

def insert_mapping(source, entry)
  lines = source.lines
  openai_index = lines.index { |line| line.match?(/\Aopenai:\s*(?:#.*)?\z/) }
  fail_safe("openai_section_missing") unless openai_index

  openai_end = ((openai_index + 1)...lines.length).find do |index|
    lines[index].match?(/\A\S/) && !lines[index].lstrip.start_with?("#")
  end || lines.length
  token_index = ((openai_index + 1)...openai_end).find do |index|
    lines[index].match?(/\A  principal_tokens:\s*(?:\[\])?\s*(?:#.*)?\z/)
  end
  fail_safe("principal_tokens_section_missing") unless token_index

  if lines[token_index].match?(/principal_tokens:\s*\[\]/)
    lines[token_index] = "  principal_tokens:\n"
    token_end = token_index + 1
    sequence_indent = 4
  else
    token_end = ((token_index + 1)...openai_end).find do |index|
      lines[index].match?(/\A  [A-Za-z0-9_][A-Za-z0-9_-]*:/)
    end || openai_end
    first_item = lines[(token_index + 1)...token_end].find { |line| line.match?(/\A\s*-\s+token_ref:/) }
    fail_safe("principal_tokens_style_invalid") unless first_item
    sequence_indent = first_item[/\A\s*/].length
  end
  sequence_prefix = " " * sequence_indent
  field_prefix = " " * (sequence_indent + 2)
  fragment = [
    "#{sequence_prefix}- token_ref: #{json_scalar(entry.fetch("token_ref"))}\n",
    "#{field_prefix}token_hash_sha256: #{json_scalar(entry.fetch("token_hash_sha256"))}\n",
    "#{field_prefix}principal_ref: #{json_scalar(entry.fetch("principal_ref"))}\n",
    "#{field_prefix}principal_alias: #{json_scalar(entry.fetch("principal_alias"))}\n"
  ]
  lines.insert(token_end, *fragment)
  lines.join
end

def run_command(*argv)
  stdout, _stderr, status = Open3.capture3(*argv)
  fail_safe("command_failed") unless status.success?
  stdout
end

def loopback_uri(profile_key, path = nil)
  base = profile.fetch(profile_key)
  uri = URI(path ? "#{base}#{path}" : base)
  fail_safe("loopback_url_invalid") unless uri.scheme == "http" && %w[127.0.0.1 ::1 localhost].include?(uri.host)
  fail_safe("loopback_url_invalid") if uri.user || uri.password || uri.fragment
  uri
end

def loopback_request(uri, request, read_timeout:)
  http = Net::HTTP.new(uri.host, uri.port, nil)
  http.open_timeout = 3
  http.read_timeout = read_timeout
  response = http.start { |client| client.request(request) }
  fail_safe("loopback_http_failed") unless response.is_a?(Net::HTTPSuccess)
  response.body.to_s
rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, SystemCallError
  fail_safe("loopback_network_failed")
end

def api_json(path, raw_token, payload = nil, read_timeout: 30)
  uri = loopback_uri("api_base_url_runner", path)
  request = payload ? Net::HTTP::Post.new(uri) : Net::HTTP::Get.new(uri)
  request["Authorization"] = "Bearer #{raw_token}"
  request["Content-Type"] = "application/json"
  request.body = JSON.generate(payload) if payload
  body = loopback_request(uri, request, read_timeout: read_timeout)
  parsed = JSON.parse(body)
  fail_safe("openai_response_invalid") unless parsed.is_a?(Hash)
  parsed
rescue JSON::ParserError
  fail_safe("openai_response_invalid")
end

def api_smoke(payload)
  raw_token = payload["raw_token"]
  fail_safe("raw_token_invalid") unless raw_token.is_a?(String) && raw_token.start_with?("iop_") && !raw_token.match?(/[\r\n]/)
  models = api_json("/models", raw_token, nil, read_timeout: 15)
  fail_safe("openai_models_invalid") unless models["data"].is_a?(Array) && !models["data"].empty?
  response = api_json(
    "/chat/completions",
    raw_token,
    {
      "model" => profile.fetch("smoke_model"),
      "messages" => [{ "role" => "user", "content" => "Reply with the single word OK." }],
      "max_tokens" => 2048,
      "temperature" => 0
    },
    read_timeout: 120
  )
  choice = response["choices"].is_a?(Array) ? response["choices"].first : nil
  message = choice.is_a?(Hash) ? choice["message"] : nil
  content = message.is_a?(Hash) ? message["content"] : nil
  fail_safe("openai_chat_invalid") unless content.is_a?(String) && !content.strip.empty? && choice["finish_reason"]
  { "status" => "passed" }
end

def metrics_observed(payload)
  token_ref = payload["token_ref"]
  fail_safe("token_ref_invalid") unless token_ref.is_a?(String) && token_ref.match?(/\A[a-z0-9][a-z0-9._:-]{2,79}\z/)
  uri = loopback_uri("metrics_url_runner")
  body = loopback_request(uri, Net::HTTP::Get.new(uri), read_timeout: 8)
  { "status" => "ok", "observed" => body.match?(/token_ref="#{Regexp.escape(token_ref)}"/) }
end

def parse_refresh_status(stdout)
  parsed = begin
    value = JSON.parse(stdout)
    value if value.is_a?(Hash) && value.key?("status")
  rescue JSON::ParserError
    nil
  end
  unless parsed
    stdout.lines.reverse_each do |line|
      begin
        value = JSON.parse(line)
        if value.is_a?(Hash) && value.key?("status")
          parsed = value
          break
        end
      rescue JSON::ParserError
        next
      end
    end
  end
  fail_safe("refresh_response_invalid") unless parsed
  parsed.fetch("status").to_s
end

def refresh_status(candidate_path)
  stdout = run_command(
    edge_bin,
    "--config", config_path,
    "config", "refresh",
    "--addr", admin_addr,
    "--config-path", candidate_path,
    "--mode", "dry-run"
  )
  parse_refresh_status(stdout)
end

def listener_pid
  stdout, _stderr, status = Open3.capture3("lsof", "-tiTCP:#{listener_port}", "-sTCP:LISTEN")
  return nil unless status.success?
  values = stdout.lines.map(&:strip).reject(&:empty?).uniq
  fail_safe("edge_listener_ambiguous") unless values.length == 1
  value = values.first
  fail_safe("edge_listener_pid_invalid") unless value.match?(/\A\d+\z/)
  value.to_i
end

def expected_listener_pid
  pid = listener_pid
  return nil unless pid
  stdout, _stderr, status = Open3.capture3("ps", "-p", pid.to_s, "-o", "command=")
  fail_safe("edge_listener_identity_unavailable") unless status.success?
  fail_safe("edge_listener_unexpected") unless stdout.strip == expected_command
  pid
end

def healthy?
  uri = health_uri
  response = Net::HTTP.start(uri.host, uri.port, open_timeout: 2, read_timeout: 2) do |http|
    http.get(uri.request_uri)
  end
  response.is_a?(Net::HTTPSuccess)
rescue StandardError
  false
end

def wait_until(timeout_seconds)
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds
  loop do
    return true if yield
    return false if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
    sleep 0.25
  end
end

def stop_edge
  pid = expected_listener_pid
  return unless pid

  Process.kill("TERM", pid)
  return if wait_until(10) { expected_listener_pid != pid }

  Process.kill("KILL", pid)
  fail_safe("edge_stop_failed") unless wait_until(5) { expected_listener_pid != pid }
rescue Errno::ESRCH
  nil
rescue Errno::EPERM
  fail_safe("edge_stop_denied")
end

def start_edge
  FileUtils.mkdir_p(File.dirname(log_path))
  log = File.open(log_path, "a", 0o600)
  File.chmod(0o600, log_path)
  begin
    pid = Process.spawn(
      NOHUP_BIN, edge_bin, "--config", config_arg, "serve",
      chdir: runtime_root,
      in: File::NULL,
      out: log,
      err: log,
      pgroup: true
    )
  ensure
    log.close
  end
  Process.detach(pid)
  fail_safe("edge_readiness_failed") unless wait_until(30) { expected_listener_pid == pid && healthy? }
end

def restart_edge
  stop_edge
  start_edge
end

def cutover_files(config_path, candidate_path, backup_path)
  File.rename(config_path, backup_path)
  begin
    File.rename(candidate_path, config_path)
  rescue StandardError
    File.rename(backup_path, config_path) if File.file?(backup_path) && !File.exist?(config_path)
    raise
  end
end

def rollback_to(backup_path)
  expanded = File.expand_path(backup_path.to_s)
  allowed_prefix = File.dirname(config_path) + File::SEPARATOR
  fail_safe("backup_path_invalid") unless expanded.start_with?(allowed_prefix)
  fail_safe("backup_path_invalid") unless File.basename(expanded).match?(/\Aedge\.yaml\.before-token-\d{8}T\d{6}Z-[0-9a-f]{6}\.yaml\z/)
  fail_safe("backup_missing") unless File.file?(expanded)
  run_command(edge_bin, "--config", expanded, "config", "check")

  failed_path = "#{config_path}.failed-token-#{Time.now.utc.strftime("%Y%m%dT%H%M%SZ")}-#{SecureRandom.hex(3)}.yaml"
  stop_edge
  File.rename(config_path, failed_path) if File.exist?(config_path)
  File.rename(expanded, config_path)
  start_edge
  fail_safe("rollback_config_invalid") unless refresh_status(config_path) == "applied"
  { "status" => "rolled_back" }
end

def inspect_state(payload)
  fail_safe("runtime_assets_invalid") unless File.executable?(edge_bin) && File.executable?(NOHUP_BIN)
  fail_safe("runtime_directory_not_writable") unless File.writable?(File.dirname(config_path))
  config = load_config
  items = mappings(config)
  validate_mappings!(items)
  probe_entry = {
    "token_ref" => "iop-#{profile.fetch("environment")}-preflight-#{SecureRandom.hex(6)}",
    "token_hash_sha256" => Digest::SHA256.hexdigest(SecureRandom.random_bytes(32)),
    "principal_ref" => "preflight-shape.invalid",
    "principal_alias" => "preflight-shape"
  }
  probe_config = YAML.safe_load(insert_mapping(File.read(config_path), probe_entry), aliases: true)
  probe_items = mappings(probe_config)
  validate_mappings!(probe_items)
  fail_safe("candidate_shape_invalid") unless probe_items.length == items.length + 1
  current_refresh = payload["refresh_probe"] ? refresh_status(config_path) : nil
  fail_safe("current_refresh_invalid") if current_refresh && current_refresh != "applied"
  {
    "status" => "ok",
    "mapping_count" => items.length,
    "mappings" => items.map do |item|
      {
        "token_ref" => item["token_ref"].to_s,
        "token_hash_sha256" => item["token_hash_sha256"].to_s.downcase,
        "principal_ref" => item["principal_ref"].to_s,
        "principal_alias" => item["principal_alias"].to_s
      }
    end,
    "candidate_shape" => true,
    "refresh_status" => current_refresh,
    "healthy" => healthy?,
    "listener" => !expected_listener_pid.nil?
  }
end

def candidate_check
  active_items = mappings(load_config)
  probe_entry = {
    "token_ref" => "iop-#{profile.fetch("environment")}-candidate-check-#{SecureRandom.hex(6)}",
    "token_hash_sha256" => Digest::SHA256.hexdigest(SecureRandom.random_bytes(32)),
    "principal_ref" => "candidate-check.invalid",
    "principal_alias" => "candidate-check"
  }
  candidate_path = File.join(File.dirname(config_path), ".edge-token-candidate-check-#{SecureRandom.hex(6)}.yaml")
  begin
    File.open(candidate_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
      file.write(insert_mapping(File.read(config_path), probe_entry))
      file.flush
      file.fsync
    end
    candidate_items = mappings(load_config(candidate_path))
    validate_mappings!(candidate_items)
    fail_safe("candidate_count_invalid") unless candidate_items.length == active_items.length + 1
    run_command(edge_bin, "--config", candidate_path, "config", "check")
    fail_safe("restart_required_not_reported") unless refresh_status(candidate_path) == "restart_required"
    { "status" => "validated" }
  ensure
    File.delete(candidate_path) if File.exist?(candidate_path)
  end
end

def apply(payload)
  entry = payload.fetch("entry")
  fail_safe("entry_invalid") unless entry.is_a?(Hash)
  fail_safe("hash_invalid") unless entry["token_hash_sha256"].to_s.match?(/\A[0-9a-f]{64}\z/)
  %w[token_ref principal_ref principal_alias].each do |key|
    value = entry[key].to_s
    fail_safe("entry_invalid") if value.empty? || value.include?("\n") || value.include?("\r")
  end

  active = load_config
  active_items = mappings(active)
  validate_mappings!(active_items)
  fail_safe("principal_conflict") if active_items.any? { |item| item["principal_ref"].to_s == entry["principal_ref"].to_s }
  fail_safe("token_ref_conflict") if active_items.any? { |item| item["token_ref"].to_s == entry["token_ref"].to_s }
  fail_safe("token_hash_conflict") if active_items.any? { |item| item["token_hash_sha256"].to_s.casecmp?(entry["token_hash_sha256"].to_s) }

  candidate_path = nil
  candidate_items = nil
  backup_path = nil
  original_moved = false
  begin
    source = File.read(config_path)
    candidate_path = File.join(
      File.dirname(config_path),
      ".edge-token-issue-#{Time.now.utc.strftime("%Y%m%dT%H%M%SZ")}-#{SecureRandom.hex(3)}.yaml"
    )
    File.open(candidate_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
      file.write(insert_mapping(source, entry))
      file.flush
      file.fsync
    end

    candidate = load_config(candidate_path)
    candidate_items = mappings(candidate)
    validate_mappings!(candidate_items)
    fail_safe("candidate_count_invalid") unless candidate_items.length == active_items.length + 1
    run_command(edge_bin, "--config", candidate_path, "config", "check")
    fail_safe("restart_required_not_reported") unless refresh_status(candidate_path) == "restart_required"

    backup_path = "#{config_path}.before-token-#{Time.now.utc.strftime("%Y%m%dT%H%M%SZ")}-#{SecureRandom.hex(3)}.yaml"
    cutover_files(config_path, candidate_path, backup_path)
    original_moved = true
    candidate_path = nil
    restart_edge
    current = mappings(load_config)
    validate_mappings!(current)
    expected = current.count do |item|
      item["principal_ref"].to_s == entry["principal_ref"].to_s &&
        item["token_ref"].to_s == entry["token_ref"].to_s &&
        item["token_hash_sha256"].to_s.casecmp?(entry["token_hash_sha256"].to_s)
    end
    fail_safe("active_mapping_invalid") unless expected == 1
    fail_safe("post_restart_refresh_invalid") unless refresh_status(config_path) == "applied"
  rescue StandardError
    if original_moved && backup_path && File.file?(backup_path)
      begin
        rollback_to(backup_path)
      rescue StandardError
        fail_safe("automatic_rollback_failed")
      end
    end
    raise
  ensure
    File.delete(candidate_path) if candidate_path && File.exist?(candidate_path)
  end

  {
    "status" => "activated",
    "backup_path" => backup_path,
    "mapping_count" => candidate_items.length
  }
end

def selftest
  PROFILES.each_value do |item|
    fail_safe("selftest_profile_path_failed") unless item.fetch("config_path").start_with?(item.fetch("runtime_root") + File::SEPARATOR)
    fail_safe("selftest_profile_command_failed") unless item.fetch("edge_bin").start_with?(item.fetch("runtime_root") + File::SEPARATOR)
    uri = URI(item.fetch("api_base_url_runner"))
    fail_safe("selftest_profile_url_failed") unless uri.scheme == "http" && uri.host == "127.0.0.1"
  end
  sample = <<~YAML
    version: 1
    openai:
      enabled: true
      principal_tokens: []
      timeout_sec: 30
  YAML
  entry = {
    "token_ref" => "iop-dev-corp-sample",
    "token_hash_sha256" => "a" * 64,
    "principal_ref" => "sample.invalid",
    "principal_alias" => "sample"
  }
  inserted = YAML.safe_load(insert_mapping(sample, entry), aliases: true)
  items = mappings(inserted)
  validate_mappings!(items)
  fail_safe("selftest_insert_failed") unless items.length == 1
  indentless = <<~YAML
    version: 1
    openai:
      enabled: true
      principal_tokens:
      - token_ref: "existing"
        token_hash_sha256: "#{"b" * 64}"
        principal_ref: "existing.invalid"
        principal_alias: "existing"
    a2a:
      enabled: false
  YAML
  indentless_inserted = insert_mapping(indentless, entry)
  fail_safe("selftest_indentless_style_failed") unless indentless_inserted.include?("\n  - token_ref: \"iop-dev-corp-sample\"\n")
  indentless_items = mappings(YAML.safe_load(indentless_inserted, aliases: true))
  validate_mappings!(indentless_items)
  fail_safe("selftest_indentless_insert_failed") unless indentless_items.length == 2
  applied = JSON.pretty_generate({ "status" => "applied", "restart_required_paths" => ["openai.principal_tokens"] })
  fail_safe("selftest_status_failed") unless parse_refresh_status(applied) == "applied"
  Dir.mktmpdir("iop-token-cutover-") do |directory|
    config_path = File.join(directory, "edge.yaml")
    missing_candidate = File.join(directory, "missing.yaml")
    backup_path = File.join(directory, "edge.yaml.backup")
    File.write(config_path, "original")
    begin
      cutover_files(config_path, missing_candidate, backup_path)
      fail_safe("selftest_cutover_failed")
    rescue Errno::ENOENT
      fail_safe("selftest_cutover_restore_failed") unless File.read(config_path) == "original"
      fail_safe("selftest_cutover_backup_leaked") if File.exist?(backup_path)
    end
  end
  { "status" => "ok" }
end

begin
  payload = read_payload
  $profile = PROFILES.fetch(payload.fetch("environment"))
  result = case payload.fetch("action")
           when "inspect" then inspect_state(payload)
           when "candidate-check" then with_transaction_lock { candidate_check }
           when "apply" then with_transaction_lock { apply(payload) }
           when "rollback" then with_transaction_lock { rollback_to(payload.fetch("backup_path")) }
           when "api-smoke" then api_smoke(payload)
           when "metrics" then metrics_observed(payload)
           when "selftest" then selftest
           end
  puts JSON.generate(result)
rescue SafeFailure => error
  puts JSON.generate({ "status" => "blocked", "code" => error.code })
  exit 2
rescue KeyError
  puts JSON.generate({ "status" => "blocked", "code" => "missing_input" })
  exit 2
rescue StandardError
  puts JSON.generate({ "status" => "blocked", "code" => "unexpected_remote_failure" })
  exit 2
end
