export type ApiBaseUrl = string

export const DEFAULT_API_BASE_URL: ApiBaseUrl =
  (process.env.DEFAULT_API_BASE_URL) || 'http://bruno.fantasticpartner.com/api/'

export type UploadDocumentParams = {
  user: string
  session: string
  prop?: string
  file: File
  baseUrl?: ApiBaseUrl
  onProgress?: (fraction: number) => void
  signal?: AbortSignal
}

export type LoadAttachmentsParams = {
  id: string
  session: string
  prop?: string
  baseUrl?: ApiBaseUrl
}

export type DeleteAttachmentParams = {
  id: string
  session: string
  prop?: string
  filename: string
  baseUrl?: ApiBaseUrl
}

export type ApiUploadResponse = {
  ok?: boolean
  files?: string[]
}

export type ApiDeleteAttachmentResponse = {
  ok?: boolean
  deleted?: boolean
  file?: string
}

export type ApiAttachmentsListResponse = Record<string, string[]>

export class ApiError extends Error {
  status: number | undefined

  constructor(message: string, status?: number) {
    super(message)
    this.name = 'ApiError'
    this.status = status
  }
}

function apiUrl(baseUrl: ApiBaseUrl | undefined, path: string): string {
  const base = (baseUrl || DEFAULT_API_BASE_URL).replace(/\/+$/, '')
  const p = path.startsWith('/') ? path : `/${path}`
  return `${base}${p}`
}

async function parseJsonSafe(response: Response): Promise<unknown> {
  const text = await response.text()
  if (!text) return null
  try {
    return JSON.parse(text) as unknown
  } catch {
    return text
  }
}

function normalizeUploadedFiles(payload: unknown): string[] {
  if (!payload || typeof payload !== 'object') return []

  const files = (payload as { files?: unknown }).files
  if (!files) return []

  if (Array.isArray(files)) {
    const out: string[] = []

    for (const item of files) {
      if (typeof item === 'string') {
        out.push(item)
        continue
      }
      if (item && typeof item === 'object') {
        const obj = item as { storedName?: unknown; originalName?: unknown; name?: unknown }
        const name =
          (typeof obj.storedName === 'string' && obj.storedName) ||
          (typeof obj.originalName === 'string' && obj.originalName) ||
          (typeof obj.name === 'string' && obj.name) ||
          ''
        if (name) out.push(name)
      }
    }

    return out
  }

  return []
}

export async function uploadDocument(params: UploadDocumentParams): Promise<ApiUploadResponse> {
  const { user, session, prop, file, baseUrl, onProgress, signal } = params

  if (!user) throw new ApiError('missing user')
  if (!session) throw new ApiError('missing session')
  if (!file) throw new ApiError('missing file')

  const url = apiUrl(baseUrl, '/upload')

  return await new Promise<ApiUploadResponse>((resolve, reject) => {
    const xhr = new XMLHttpRequest()

    if (signal) {
      if (signal.aborted) {
        reject(new ApiError('aborted'))
        return
      }
      signal.addEventListener(
        'abort',
        () => {
          try {
            xhr.abort()
          } catch {
            // ignore
          }
        },
        { once: true }
      )
    }

    xhr.upload.onprogress = (e) => {
      if (!onProgress) return
      if (!e.lengthComputable) return
      const fraction = e.total > 0 ? e.loaded / e.total : 0
      onProgress(Math.max(0, Math.min(1, fraction)))
    }

    xhr.onload = () => {
      const ok = xhr.status >= 200 && xhr.status < 300
      let parsed: unknown = null
      try {
        parsed = xhr.responseText ? (JSON.parse(xhr.responseText) as unknown) : null
      } catch {
        parsed = xhr.responseText
      }

      if (!ok) {
        reject(new ApiError(`Upload failed (HTTP ${xhr.status})`, xhr.status))
        return
      }

      const files = normalizeUploadedFiles(parsed)
      resolve({ ok: true, files })
    }

    xhr.onerror = () => {
      reject(new ApiError('Upload failed (network error)'))
    }

    xhr.onabort = () => {
      reject(new ApiError('Upload cancelled'))
    }

    const fd = new FormData()
    fd.append('user', user)
    fd.append('session', session)
    if (prop) fd.append('prop', prop)
    fd.append('documents', file)

    xhr.open('POST', url)
    xhr.send(fd)
  })
}

export async function loadAttachments(params: LoadAttachmentsParams): Promise<string[]> {
  const { id, session, prop, baseUrl } = params

  if (!id) throw new ApiError('missing id')
  if (!session) throw new ApiError('missing session')

  const res = await fetch(apiUrl(baseUrl, '/loadattachments'), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id, session, prop }),
  })

  if (!res.ok) {
    const body = await parseJsonSafe(res)
    const msg = typeof body === 'string' ? body : `Load attachments failed (HTTP ${res.status})`
    throw new ApiError(msg, res.status)
  }

  const data = (await res.json()) as unknown
  if (Array.isArray(data) && data.every((x) => typeof x === 'string')) {
    return data
  }

  return []
}

export async function deleteAttachment(params: DeleteAttachmentParams): Promise<ApiDeleteAttachmentResponse> {
  const { id, session, prop, filename, baseUrl } = params

  if (!id) throw new ApiError('missing id')
  if (!session) throw new ApiError('missing session')
  if (!filename) throw new ApiError('missing filename')

  const res = await fetch(apiUrl(baseUrl, '/deleteattachment'), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id, session, prop, filename }),
  })

  if (!res.ok) {
    const body = await parseJsonSafe(res)
    const msg = typeof body === 'string' ? body : `Delete attachment failed (HTTP ${res.status})`
    throw new ApiError(msg, res.status)
  }

  const data = (await res.json()) as unknown
  return (data && typeof data === 'object' ? (data as ApiDeleteAttachmentResponse) : {})
}

export async function loadAttachmentsList(params: LoadAttachmentsParams): Promise<ApiAttachmentsListResponse> {
  const { id, session, prop, baseUrl } = params

  if (!id) throw new ApiError('missing id')
  if (!session) throw new ApiError('missing session')
  if (prop) throw new ApiError('prop is not supported for loadAttachmentsList')

  const res = await fetch(apiUrl(baseUrl, '/loadattachmentslist'), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ id, session }),
  })

  if (!res.ok) {
    const body = await parseJsonSafe(res)
    const msg = typeof body === 'string' ? body : `Load attachments list failed (HTTP ${res.status})`
    throw new ApiError(msg, res.status)
  }

  const data = (await res.json()) as unknown
  if (!data || typeof data !== 'object' || Array.isArray(data)) {
    return {}
  }

  const result: ApiAttachmentsListResponse = {}
  for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
    if (!key) continue
    if (Array.isArray(value) && value.every((x) => typeof x === 'string')) {
      result[key] = value
    }
  }

  return result
}
