import fs from 'node:fs'
import path from 'node:path'

import { PROJECT_ROOT } from '../../../config/runtime.js'

const CLOUDTENTACLES_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'cloudtentacles-sources.json')

type JsonObject = Record<string, any>

export function getCloudtentaclesSourcesFilePath() {
  return CLOUDTENTACLES_SOURCES_FILE_PATH
}

/**
 * Backward-compatible: returns the source with key='default'.
 * Old callers that expect a single source object still work.
 */
export function getCloudtentaclesSourceConfig() {
  const config = loadCloudtentaclesSourcesConfigFromFile()
  const defaultSource = config.sources.find(s => s.key === 'default')
  return defaultSource || normalizeCloudtentaclesSourceItem({ key: 'default' })
}

/**
 * Get a source item by its key. Returns null if not found.
 */
export function getCloudtentaclesSourceByKey(sourceKey: unknown) {
  const config = loadCloudtentaclesSourcesConfigFromFile()
  const key = String(sourceKey || '').trim()
  if (!key) return null
  return config.sources.find(s => s.key === key) || null
}

/**
 * Return the entire normalized config: { enabled, sources }.
 */
export function listCloudtentaclesSources() {
  return loadCloudtentaclesSourcesConfigFromFile()
}

/**
 * Backward-compatible save: accepts both old single-object format
 * and new list format, normalizes, and persists.
 */
export function saveCloudtentaclesSourceConfig(rawValue: unknown) {
  const normalized = normalizeCloudtentaclesSourcesConfig(rawValue)
  fs.mkdirSync(path.dirname(CLOUDTENTACLES_SOURCES_FILE_PATH), { recursive: true })
  fs.writeFileSync(CLOUDTENTACLES_SOURCES_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
  return normalized
}

/**
 * Save the entire list-format config object: { enabled, sources: [...] }.
 */
export function saveCloudtentaclesSourcesList(rawValue: unknown) {
  return saveCloudtentaclesSourceConfig(rawValue)
}

/**
 * Save or update a single source item identified by sourceKey.
 * If a source with the same key exists, it is replaced; otherwise it is appended.
 */
export function saveCloudtentaclesSourceByKey(sourceKey: unknown, data: JsonObject = {}) {
  const key = String(sourceKey || '').trim()
  if (!key) {
    throw new Error('saveCloudtentaclesSourceByKey: sourceKey is required')
  }

  const config = loadCloudtentaclesSourcesConfigFromFile()
  const normalizedItem = normalizeCloudtentaclesSourceItem({ ...data, key })
  const existingIndex = config.sources.findIndex(s => s.key === key)

  if (existingIndex >= 0) {
    config.sources[existingIndex] = normalizedItem
  } else {
    config.sources.push(normalizedItem)
  }

  fs.mkdirSync(path.dirname(CLOUDTENTACLES_SOURCES_FILE_PATH), { recursive: true })
  fs.writeFileSync(CLOUDTENTACLES_SOURCES_FILE_PATH, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
  return config
}

/**
 * Delete a single source by key. Throws if key is 'default' (cannot delete default source).
 */
export function deleteCloudtentaclesSourceByKey(sourceKey: unknown) {
  const key = String(sourceKey || '').trim()
  if (!key) {
    throw new Error('deleteCloudtentaclesSourceByKey: sourceKey is required')
  }
  if (key === 'default') {
    throw new Error('deleteCloudtentaclesSourceByKey: cannot delete the default source')
  }

  const config = loadCloudtentaclesSourcesConfigFromFile()
  const existingIndex = config.sources.findIndex(s => s.key === key)

  if (existingIndex < 0) {
    return config
  }

  config.sources.splice(existingIndex, 1)

  fs.mkdirSync(path.dirname(CLOUDTENTACLES_SOURCES_FILE_PATH), { recursive: true })
  fs.writeFileSync(CLOUDTENTACLES_SOURCES_FILE_PATH, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
  return config
}

// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------

function loadCloudtentaclesSourcesConfigFromFile() {
  if (!fs.existsSync(CLOUDTENTACLES_SOURCES_FILE_PATH)) {
    return createDefaultCloudtentaclesSourcesConfig()
  }

  try {
    const rawText = fs.readFileSync(CLOUDTENTACLES_SOURCES_FILE_PATH, 'utf8')
    return normalizeCloudtentaclesSourcesConfig(JSON.parse(rawText))
  } catch {
    return createDefaultCloudtentaclesSourcesConfig()
  }
}

/**
 * Normalize a single source item. Adds key (required) and label (optional).
 */
function normalizeCloudtentaclesSourceItem(rawValue: unknown) {
  const source = isPlainObject(rawValue) ? rawValue : {}

  return {
    key: String(source.key || 'default').trim() || 'default',
    label: String(source.label || '').trim(),
    baseUrl: String(source.baseUrl || 'https://123.207.217.176').trim() || 'https://123.207.217.176',
    username: String(source.username || '').trim(),
    password: String(source.password || '').trim(),
    phone: String(source.phone || '').trim(),
    deviceId: String(source.deviceId || '-').trim() || '-',
    deviceType: normalizeInteger(source.deviceType, 0),
  }
}

/**
 * Normalize the overall config. Handles both old single-object format
 * (auto-migrates to new list format) and new { enabled, sources } format.
 */
function normalizeCloudtentaclesSourcesConfig(rawValue: unknown) {
  // Old format: { enabled: true, username: 'xxx', ... } (single object, no sources array)
  if (isPlainObject(rawValue) && !Array.isArray(rawValue.sources)) {
    return {
      enabled: rawValue.enabled !== false,
      sources: [
        normalizeCloudtentaclesSourceItem({
          key: 'default',
          label: '默认账号',
          ...rawValue,  // old fields auto-map to default source
        }),
      ].filter(Boolean),
    }
  }

  // New format: { enabled, sources: [...] }
  return {
    enabled: isPlainObject(rawValue) ? rawValue.enabled !== false : true,
    sources: isPlainObject(rawValue) && Array.isArray(rawValue.sources)
      ? rawValue.sources.map(s => normalizeCloudtentaclesSourceItem(s)).filter(Boolean)
      : [],
  }
}

function createDefaultCloudtentaclesSourcesConfig() {
  return {
    enabled: true,
    sources: [
      normalizeCloudtentaclesSourceItem({ key: 'default', label: '默认账号' }),
    ],
  }
}

function normalizeInteger(value: unknown, fallback: number) {
  const parsed = Number(value)
  return Number.isInteger(parsed) ? parsed : fallback
}

function isPlainObject(value: unknown): value is JsonObject {
  return Object.prototype.toString.call(value) === '[object Object]'
}
