import refreshTokenHelper from "../../utils/refreshTokenHelper"
import forceLogoutHelper from "../../utils/forceLogoutHelper"
import errorHandlingHelper from "../../utils/errorHandlingHelper"
import maybeZugferdPdf from "../../utils/zugferd/maybeZugferdPdf"
import toIdempiereDate from "../../utils/idempiereDate"

// Lexware lexoffice API configuration
const LEXOFFICE_API_URL = "https://api.lexware.io"
const LEXOFFICE_API_KEY = "EVrdrnU7rEhJp93WAJ8MhwPAP.tYOwKTj27Ep2D8fTelLnIJ"

// Log collector for debugging
const createLogger = () => {
  const logs: string[] = []
  return {
    log: (message: string) => {
      const timestamp = new Date().toISOString()
      logs.push(`[${timestamp}] ${message}`)
    },
    getLogs: () => logs
  }
}

// Helper to make lexoffice API calls
const lexofficeRequest = async (endpoint: string, method: string, body?: any, logger?: ReturnType<typeof createLogger>) => {
  const headers: Record<string, string> = {
    'Authorization': `Bearer ${LEXOFFICE_API_KEY}`,
    'Accept': 'application/json'
  }

  if (body) {
    headers['Content-Type'] = 'application/json'
  }

  const options: any = {
    method,
    headers
  }

  if (body) {
    options.body = JSON.stringify(body)
  }

  logger?.log(`Lexoffice API: ${method} ${endpoint}`)

  const response = await fetch(`${LEXOFFICE_API_URL}${endpoint}`, options)

  if (!response.ok) {
    const errorText = await response.text()
    logger?.log(`Lexoffice API error (${response.status}): ${errorText}`)
    throw new Error(`Lexoffice API error (${response.status}): ${errorText}`)
  }

  const contentType = response.headers.get('content-type')
  if (contentType && contentType.includes('application/json')) {
    const data = await response.json()
    logger?.log(`Lexoffice API response: ${JSON.stringify(data).substring(0, 200)}...`)
    return data
  }

  const locationHeader = response.headers.get('Location')
  if (locationHeader) {
    const id = locationHeader.split('/').pop()
    logger?.log(`Lexoffice API: Created resource with ID ${id}`)
    return { id, status: response.status }
  }

  return { status: response.status, statusText: response.statusText }
}

// Query lexoffice contacts by name
const findContact = async (name: string, logger?: ReturnType<typeof createLogger>): Promise<any | null> => {
  try {
    if (name && name.length >= 3) {
      logger?.log(`Searching for contact: "${name}"`)
      const result = await lexofficeRequest(`/v1/contacts?name=${encodeURIComponent(name)}`, 'GET', undefined, logger)
      if (result?.content?.length > 0) {
        logger?.log(`Found existing contact: ${result.content[0].id}`)
        return result.content[0]
      }
      logger?.log(`No contact found for: "${name}"`)
    }
    return null
  } catch (err: any) {
    logger?.log(`Error searching contact: ${err.message}`)
    return null
  }
}

// Create a new contact in lexoffice
const createLexofficeContact = async (partner: any, logger?: ReturnType<typeof createLogger>): Promise<any> => {
  const partnerName = partner.Name || ''
  const partnerName2 = partner.Name2 || ''
  const isCustomer = partner.IsCustomer === true || partner.IsCustomer === 'Y'
  const isVendor = partner.IsVendor === true || partner.IsVendor === 'Y'

  logger?.log(`Creating contact for: "${partnerName}" (Customer: ${isCustomer}, Vendor: ${isVendor})`)

  const roles: any = {}
  if (isCustomer) roles.customer = {}
  if (isVendor) roles.vendor = {}
  if (!isCustomer && !isVendor) roles.customer = {}

  let contactData: any = {
    version: 0,
    roles
  }

  if (partner.EMail) {
    contactData.emailAddresses = { business: [partner.EMail] }
  }

  // Use "LogShip" instead of "iDempiere", include customer number (Value)
  contactData.note = `LogShip Customer#: ${partner.Value || ''} (ID: ${partner.id || ''})`

  if (partnerName2) {
    const nameParts = partnerName2.trim().split(' ')
    contactData.company = {
      name: partnerName,
      contactPersons: [{
        firstName: nameParts[0] || '',
        lastName: nameParts.slice(1).join(' ') || partnerName2
      }]
    }
  } else {
    const companyIndicators = ['GmbH', 'AG', 'KG', 'OHG', 'Ltd', 'Inc', 'LLC', 'UG', 'e.K.', 'Co.', '&', 'mbH']
    const isCompany = companyIndicators.some(indicator => partnerName.includes(indicator))

    if (isCompany) {
      contactData.company = { name: partnerName }
    } else {
      const nameParts = partnerName.trim().split(' ')
      if (nameParts.length > 1) {
        contactData.person = {
          firstName: nameParts[0],
          lastName: nameParts.slice(1).join(' ')
        }
      } else {
        contactData.person = { lastName: partnerName }
      }
    }
  }

  logger?.log(`Contact data: ${JSON.stringify(contactData)}`)
  const result = await lexofficeRequest('/v1/contacts', 'POST', contactData, logger)
  return result
}

// Upload file to lexoffice as voucher
const uploadFileToLexoffice = async (fileBuffer: Buffer, fileName: string, logger?: ReturnType<typeof createLogger>): Promise<any> => {
  logger?.log(`Uploading file: ${fileName} (${fileBuffer.length} bytes)`)

  // Use native FormData and Blob for Node.js 18+
  const blob = new Blob([fileBuffer], { type: 'application/pdf' })
  const formData = new FormData()
  formData.append('file', blob, fileName)
  formData.append('type', 'voucher')

  logger?.log(`FormData created with file blob and type=voucher`)

  const response = await fetch(`${LEXOFFICE_API_URL}/v1/files`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${LEXOFFICE_API_KEY}`,
      'Accept': 'application/json'
    },
    body: formData
  })

  if (!response.ok) {
    const errorText = await response.text()
    logger?.log(`File upload failed (${response.status}): ${errorText}`)
    throw new Error(`File upload failed (${response.status}): ${errorText}`)
  }

  const locationHeader = response.headers.get('Location')
  let responseData: any = {}
  try {
    responseData = await response.json()
  } catch (e) {}

  const fileId = responseData.id || locationHeader?.split('/').pop()
  logger?.log(`File uploaded successfully: ${fileId}`)

  return {
    id: fileId,
    ...responseData
  }
}

const handleFunc = async (event: any, authToken: any = null) => {
  const logger = createLogger()
  let data: any = {}
  const token = authToken ?? await getTokenHelper(event)
  const body = await readBody(event)
  const invoiceIds: number[] = body.ids || []

  logger.log(`Starting Lexoffice upload for invoice IDs: ${JSON.stringify(invoiceIds)}`)

  if (!invoiceIds.length) {
    return { status: 400, message: 'No invoice IDs provided', logs: logger.getLogs() }
  }

  const errors: string[] = []
  const skipped: string[] = []
  let uploadedCount = 0

  for (const invoiceId of invoiceIds) {
    try {
      logger.log(`--- Processing invoice ID: ${invoiceId} ---`)

      // Fetch invoice (no $expand)
      const invoice: any = await event.context.fetch(
        `models/c_invoice/${invoiceId}`,
        'GET',
        token,
        null
      )

      if (!invoice) {
        const msg = `Invoice ${invoiceId} not found`
        logger.log(msg)
        errors.push(msg)
        continue
      }

      logger.log(`Invoice found: ${invoice.DocumentNo} (DocBaseType: ${invoice.DocBaseType?.id || invoice.DocBaseType})`)

      // Check if already uploaded to Lexoffice
      if (invoice.isUploadToLexoffice === true || invoice.isUploadToLexoffice === 'Y') {
        const msg = `Invoice ${invoice.DocumentNo} already uploaded to Lexoffice - skipping`
        logger.log(msg)
        skipped.push(msg)
        continue
      }

      // Fetch partner separately
      const partnerId = invoice.C_BPartner_ID?.id || invoice.C_BPartner_ID
      if (!partnerId) {
        const msg = `Invoice ${invoice.DocumentNo}: No business partner found`
        logger.log(msg)
        errors.push(msg)
        continue
      }

      logger.log(`Fetching partner ID: ${partnerId}`)
      const partner: any = await event.context.fetch(
        `models/c_bpartner/${partnerId}`,
        'GET',
        token,
        null
      )

      if (!partner) {
        const msg = `Invoice ${invoice.DocumentNo}: Could not fetch partner`
        logger.log(msg)
        errors.push(msg)
        continue
      }

      logger.log(`Partner: ${partner.Name} (Value: ${partner.Value}, IsCustomer: ${partner.IsCustomer}, IsVendor: ${partner.IsVendor})`)

      // Step 1: Find or create contact in Lexoffice
      let lexofficeContactId: string | undefined = undefined

      // First check if partner already has lexware_contact_uuid saved
      if (partner.lexware_contact_uuid) {
        lexofficeContactId = partner.lexware_contact_uuid
        logger.log(`Partner already has lexware_contact_uuid: ${lexofficeContactId}`)
      } else {
        // Try to find existing contact by name in Lexoffice
        const existingContact = await findContact(partner.Name, logger)
        if (existingContact) {
          lexofficeContactId = existingContact.id
        } else {
          // Create new contact
          try {
            const newContact = await createLexofficeContact(partner, logger)
            lexofficeContactId = newContact.id
          } catch (contactErr: any) {
            logger.log(`Could not create contact for ${partner.Name}: ${contactErr.message}`)
          }
        }

        // Save lexoffice contact UUID back to C_BPartner
        if (lexofficeContactId) {
          try {
            logger.log(`Saving lexware_contact_uuid "${lexofficeContactId}" to c_bpartner ${partnerId}`)
            await event.context.fetch(
              `models/c_bpartner/${partnerId}`,
              'PUT',
              token,
              {
                lexware_contact_uuid: lexofficeContactId
              }
            )
            logger.log(`Successfully saved lexware_contact_uuid to c_bpartner`)
          } catch (saveErr: any) {
            logger.log(`Could not save lexware_contact_uuid to c_bpartner: ${saveErr.message}`)
          }
        }
      }

      // Rate limiting
      await new Promise(resolve => setTimeout(resolve, 500))

      // Step 2: Fetch invoice PDF and upload to Lexoffice
      try {
        logger.log(`Fetching PDF for invoice ${invoiceId}`)
        const pdfResponse: any = await event.context.fetch(
          `models/c_invoice/${invoiceId}/print?$report_type=PDF`,
          'GET',
          token,
          null
        )

        if (pdfResponse?.reportFile) {
          const pdfBuffer = Buffer.from(pdfResponse.reportFile, 'base64')
          logger.log(`PDF generated: ${pdfBuffer.length} bytes`)

          // If the partner is flagged for ZUGFeRD, merge the EN16931 XML into the
          // PDF before uploading. Fail-soft: returns the plain PDF when not flagged.
          const uploadBuffer = await maybeZugferdPdf(event, token, invoiceId, pdfBuffer)
          if (uploadBuffer !== pdfBuffer) {
            logger.log(`ZUGFeRD hybrid PDF generated: ${uploadBuffer.length} bytes`)
          }

          // Determine type from DocBaseType
          const docBaseType = invoice.DocBaseType?.id || invoice.DocBaseType?.identifier || invoice.DocBaseType || ''
          const isOutgoing = docBaseType === 'ARI' || docBaseType === 'ARC'
          const typePrefix = isOutgoing ? 'AR' : 'AP'

          logger.log(`DocBaseType: ${docBaseType}, Type: ${typePrefix}`)

          const uploadResult = await uploadFileToLexoffice(
            uploadBuffer,
            `${typePrefix}_${invoice.DocumentNo}_${partner.Name || 'Unknown'}.pdf`,
            logger
          )

          // Step 3: Mark invoice as uploaded in iDempiere
          try {
            // no milliseconds — iDempiere rejects ".123Z" datetimes
            const uploadDate = toIdempiereDate(new Date())
            logger.log(`Marking invoice ${invoiceId} as uploaded to Lexoffice`)
            await event.context.fetch(
              `models/c_invoice/${invoiceId}`,
              'PUT',
              token,
              {
                isUploadToLexoffice: true,
                UploadDateLexoffice: uploadDate
              }
            )
            logger.log(`Successfully marked invoice as uploaded`)
          } catch (markErr: any) {
            logger.log(`Could not mark invoice as uploaded: ${markErr.message}`)
          }

          logger.log(`SUCCESS: Invoice ${invoice.DocumentNo} uploaded to Lexoffice with file ID: ${uploadResult.id}`)
          uploadedCount++
        } else {
          const msg = `Invoice ${invoice.DocumentNo}: Could not generate PDF (reportFile is missing)`
          logger.log(msg)
          errors.push(msg)
        }
      } catch (pdfErr: any) {
        const msg = `Invoice ${invoice.DocumentNo}: ${pdfErr.message || 'Failed to upload'}`
        logger.log(msg)
        errors.push(msg)
      }

      // Rate limiting
      await new Promise(resolve => setTimeout(resolve, 500))

    } catch (err: any) {
      const msg = `Error processing invoice ${invoiceId}: ${err.message || 'Unknown error'}`
      logger.log(msg)
      errors.push(msg)
    }
  }

  logger.log(`=== SUMMARY: Uploaded ${uploadedCount} invoice(s), ${skipped.length} skipped, ${errors.length} error(s) ===`)

  // Output all logs to console for debugging
  console.log('\n========== LEXOFFICE UPLOAD LOGS ==========')
  logger.getLogs().forEach(log => console.log(log))
  console.log('============================================\n')

  if (uploadedCount > 0) {
    data = {
      status: 200,
      message: `Successfully uploaded ${uploadedCount} invoice(s) to Lexoffice${skipped.length > 0 ? `, ${skipped.length} skipped (already uploaded)` : ''}`,
      uploadedCount,
      skippedCount: skipped.length,
      uploadDate: new Date().toISOString(),
      errors: errors.length > 0 ? errors : undefined,
      skipped: skipped.length > 0 ? skipped : undefined,
      logs: logger.getLogs()
    }
  } else if (skipped.length > 0 && errors.length === 0) {
    data = {
      status: 200,
      message: `All ${skipped.length} invoice(s) were already uploaded to Lexoffice`,
      uploadedCount: 0,
      skippedCount: skipped.length,
      skipped,
      logs: logger.getLogs()
    }
  } else {
    data = {
      status: 400,
      message: 'No invoices were uploaded to Lexoffice',
      errors,
      skipped: skipped.length > 0 ? skipped : undefined,
      logs: logger.getLogs()
    }
  }

  return data
}

export default defineEventHandler(async (event) => {
  let data: any = {}

  try {
    data = await handleFunc(event)
  } catch (err: any) {
    try {
      let authToken: any = await refreshTokenHelper(event)
      data = await handleFunc(event, authToken)
    } catch (error) {
      data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
      forceLogoutHelper(event, data)
    }
  }

  return data
})
