const UserDocumentRepository = require('../../repositories/documents/UserDocumentRepository');
const { uploadBuffer, s3: exoS3 } = require('../../utils/exoscaleUploader');
const PDFDocument = require('pdfkit');
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
const DocumentTemplateService = require('../template/DocumentTemplateService');
const CompanyStampService = require('../stamp/company/CompanyStampService');
const db = require('../../database/database');
const fs = require('fs');
const path = require('path');
const { logger } = require('../../middleware/logger');
const puppeteer = require('puppeteer');
const { normalizeLanguageCode } = require('../../utils/languageUtils');

// Robust stream/Body -> Buffer reader (supports async iterable, web streams, node streams, buffers)
async function streamToBuffer(body) {
  if (!body) return Buffer.alloc(0);
  if (typeof body.transformToByteArray === 'function') {
    const arr = await body.transformToByteArray();
    return Buffer.from(arr);
  }
  if (typeof body.getReader === 'function') {
    const reader = body.getReader();
    const chunks = [];
    // eslint-disable-next-line no-constant-condition
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      chunks.push(Buffer.from(value));
    }
    return Buffer.concat(chunks);
  }
  if (body[Symbol.asyncIterator]) {
    const chunks = [];
    for await (const chunk of body) {
      chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
    }
    return Buffer.concat(chunks);
  }
  if (typeof body.on === 'function') {
    return new Promise((resolve, reject) => {
      const chunks = [];
      body.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
      body.on('end', () => resolve(Buffer.concat(chunks)));
      body.on('error', reject);
    });
  }
  if (Buffer.isBuffer(body)) return body;
  if (ArrayBuffer.isView(body)) return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
  if (body instanceof ArrayBuffer) return Buffer.from(body);
  throw new Error('Unsupported body type for streamToBuffer');
}

function fillTemplate(template, data) {
  return template.replace(/{{(\w+)}}/g, (_, key) => data[key] || '');
}

async function applyProfitPlanetSignatureForHtml({ html, userId }) {
  if (!html) return html;
  if (!/{{\s*profitplanetSignature\s*}}/i.test(html)) return html;
  try {
    const { tag } = await DocumentTemplateService.getProfitPlanetSignatureTag({ maxW: 300, maxH: 300 });
    return html.replace(/{{\s*profitplanetSignature\s*}}/gi, tag || '');
  } catch (e) {
    logger.warn('ContractUploadService.uploadContract:profitplanetSignature failed', { userId, msg: e.message });
    return html.replace(/{{\s*profitplanetSignature\s*}}/gi, '');
  }
}

// Build placeholder variables by combining DB profile data with any contractData overrides (for both personal/company).
async function buildTemplateVars({ userId, user_type, contractData = {}, unitOfWork }) {
  const vars = {};
  const setIfEmpty = (key, val) => {
    if (val === undefined || val === null) return;
    const str = String(val).trim();
    if (!str) return;
    if (!vars[key] || String(vars[key]).trim() === '') {
      vars[key] = str;
    }
  };

  // Initialize known placeholders to empty so replacement removes them if no value exists
  [
    'fullName','address','zip_code','city','country','phone','fullAddress','email',
    'companyFullAddress','companyName','registrationNumber','companyAddress','companyZipCode','companyCity','companyEmail','companyPhone',
    'contactPersonName','contactPersonPhone','companyCompanyName','companyRegistrationNumber','currentDate'
  ].forEach((k) => { vars[k] = ''; });

  // Current date placeholder (matches preview controller format)
  const now = new Date();
  const pad = (n) => String(n).padStart(2, '0');
  vars.currentDate = `${pad(now.getDate())}.${pad(now.getMonth() + 1)}.${now.getFullYear()} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;

  // Load base user row for email fallback
  try {
    const [userRows] = await unitOfWork.connection.query('SELECT * FROM users WHERE id = ? LIMIT 1', [userId]);
    const u = Array.isArray(userRows) ? userRows[0] : userRows;
    if (u) {
      setIfEmpty('email', u.email);
      setIfEmpty('fullName', `${u.first_name || ''} ${u.last_name || ''}`);
      setIfEmpty('address', u.address || u.street || u.street_address);
      setIfEmpty('zip_code', u.zip_code || u.zip || u.postal_code || u.postalCode);
      setIfEmpty('city', u.city || u.town);
      setIfEmpty('country', u.country);
      setIfEmpty('phone', u.phone || u.phone_secondary || u.mobile);
    }
  } catch (ignored) {}

  if (user_type === 'personal') {
    try {
      const [rows] = await unitOfWork.connection.query('SELECT * FROM personal_profiles WHERE user_id = ? LIMIT 1', [userId]);
      const p = Array.isArray(rows) ? rows[0] : rows;
      if (p) {
        setIfEmpty('fullName', `${p.first_name || ''} ${p.last_name || ''}`);
        setIfEmpty('address', p.address);
        setIfEmpty('zip_code', p.zip_code);
        setIfEmpty('city', p.city);
        setIfEmpty('country', p.country);
        setIfEmpty('phone', p.phone || p.phone_secondary);
        setIfEmpty('fullAddress', p.full_address);
      }
    } catch (ignored) {}
  } else if (user_type === 'company') {
    try {
      const [rows] = await unitOfWork.connection.query('SELECT * FROM company_profiles WHERE user_id = ? LIMIT 1', [userId]);
      const c = Array.isArray(rows) ? rows[0] : rows;
      if (c) {
        vars.companyName = c.company_name || '';
        vars.registrationNumber = c.registration_number || '';
        vars.companyAddress = c.address || '';
        vars.address = vars.companyAddress;
        vars.zip_code = c.zip_code || '';
        vars.city = c.city || '';
        vars.country = c.country || '';
        vars.contactPersonName = c.contact_person_name || '';
        vars.contactPersonPhone = c.contact_person_phone || c.phone || '';
        vars.companyEmail = c.email || c.company_email || c.contact_email || vars.email || '';
        vars.companyPhone = c.phone || c.contact_person_phone || '';

        const parts = [];
        if (vars.companyAddress) parts.push(vars.companyAddress);
        const zipCity = [vars.zip_code, vars.city].filter(Boolean).join(' ');
        if (zipCity) parts.push(zipCity);
        vars.companyFullAddress = parts.join(', ');

        // Prefixed variants used in templates
        vars.companyCompanyName = vars.companyName;
        vars.companyRegistrationNumber = vars.registrationNumber;
        vars.companyZipCode = vars.zip_code;
        vars.companyCity = vars.city;

        // Display name fallback for signature blocks
        setIfEmpty('fullName', vars.contactPersonName || vars.companyName);
      }
    } catch (ignored) {}
  }

  // Build fullAddress if still empty
  if (!vars.fullAddress) {
    const parts = [];
    if (vars.address) parts.push(vars.address);
    const zipCity = [vars.zip_code, vars.city].filter(Boolean).join(' ');
    if (zipCity) parts.push(zipCity);
    vars.fullAddress = parts.join(', ');
  }

  // Overlay contractData to allow request-provided values to win
  if (contractData && typeof contractData === 'object') {
    Object.entries(contractData).forEach(([key, value]) => {
      if (value === undefined || value === null) return;
      // Ignore nested objects like confirmations
      if (typeof value === 'object') return;
      vars[key] = String(value);
    });
  }

  return vars;
}

const DEBUG_PDF_FILES = !!process.env.DEBUG_PDF_FILES;
function ensureDebugDir() {
  const debugDir = path.join(__dirname, '../debug-pdf');
  if (!fs.existsSync(debugDir)) fs.mkdirSync(debugDir, { recursive: true });
  return debugDir;
}
function saveDebugFile(filename, data) {
  if (!DEBUG_PDF_FILES) return;
  const p = path.join(ensureDebugDir(), filename);
  try {
    fs.writeFileSync(p, data);
    console.log(`[ContractUploadService][DEBUG] wrote ${p}`);
  } catch (e) {
    console.error('[ContractUploadService][DEBUG] failed to write', p, e);
  }
}

// Minimal stream reader supporting AWS SDK Body
async function streamToString(stream, id) {
  const chunks = [];
  let total = 0;
  if (stream[Symbol.asyncIterator]) {
    for await (const chunk of stream) {
      const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      chunks.push(buf); total += buf.length;
    }
  } else {
    for await (const chunk of stream) {
      const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      chunks.push(buf); total += buf.length;
    }
  }
  const buffer = Buffer.concat(chunks);
  if (DEBUG_PDF_FILES || total < 32) saveDebugFile(`contract_template_${id}_html_raw.bin`, buffer);
  console.log(`[ContractUploadService] read template ${id} bytes=${total}`);
  return buffer.toString('utf-8');
}

class ContractUploadService {
  static async uploadContract({
    userId,
    file,
    documentType,
    contractCategory,
    unitOfWork,
    contractData,
    signatureImage,
    contractTemplate,
    templateId,
    lang,
    contract_type = 'contract',
    user_type = 'personal'
  }) {
    const resolvedLanguage = normalizeLanguageCode(
      lang
        ?? contractData?.lang
        ?? contractData?.language
        ?? contractData?.locale
        ?? contractData?.preferred_language
        ?? contractData?.preferredLanguage
        ?? ''
    ) || null;

    logger.info('ContractUploadService.uploadContract:start', {
      userId,
      documentType,
      contractCategory,
      templateId,
      lang: resolvedLanguage,
    });
    let pdfBuffer, originalFilename, mimeType, fileSize;
    let contractBody;

    const allowedContractTypes = new Set(['contract', 'gdpr', 'abo']);
    const normalizedContractType = (contract_type === undefined || contract_type === null)
      ? 'contract'
      : String(contract_type).trim().toLowerCase();
    contract_type = allowedContractTypes.has(normalizedContractType) ? normalizedContractType : 'contract';

    try {
      // If templateId and lang are provided, fetch HTML template from object storage
      if (templateId && resolvedLanguage) {
        logger.info('ContractUploadService.uploadContract:fetching_template', { templateId, lang: resolvedLanguage });
        const templateMeta = await DocumentTemplateService.getTemplate(templateId);
        if (!templateMeta || normalizeLanguageCode(templateMeta.lang) !== resolvedLanguage) {
          throw new Error('Template not found for specified language');
        }
        // Fetch HTML from object storage
        const s3 = new S3Client({ region: process.env.EXOSCALE_REGION });
        const getObj = await s3.send(new GetObjectCommand({
          Bucket: process.env.AWS_BUCKET,
          Key: templateMeta.storageKey
        }));
        const htmlBuffer = await streamToBuffer(getObj.Body);
        let htmlTemplate = htmlBuffer.toString('utf-8');
        // Fill variables in HTML template
        contractBody = fillTemplate(htmlTemplate, contractData);
        logger.info('ContractUploadService.uploadContract:template_fetched', { templateId });
      } else if (contractTemplate) {
        logger.info('ContractUploadService.uploadContract:using_contractTemplate');
        contractBody = fillTemplate(contractTemplate, contractData);
      } else if (contractData) {
        logger.info('ContractUploadService.uploadContract:using_contractData');
        contractBody = `Contract for ${contractData.name}\nDate: ${contractData.date}\nEmail: ${contractData.email}\n\nTerms and Conditions...\n`;
      }

      const hasExplicitTemplate = !!(templateId || contractTemplate);
      if (contractData && signatureImage && hasExplicitTemplate) {
        logger.info('ContractUploadService.uploadContract:generating_pdf', { userId });
        // Generate styled PDF
        const doc = new PDFDocument({
          size: 'A4',
          margins: { top: 60, bottom: 60, left: 72, right: 72 }
        });

        // Header
        doc.font('Helvetica-Bold').fontSize(20).text('Contract Agreement', { align: 'center' });
        doc.moveDown(1.5);
        doc.text(`Email: ${contractData.email}`);
        doc.text(`Date: ${contractData.date}`);
        doc.moveDown();

        // Contract Body Section
        doc.font('Times-Roman').fontSize(13);
        doc.text(contractBody, {
          align: 'justify',
          lineGap: 4
        });
        doc.moveDown(2);

        // Signature Section
        doc.font('Helvetica-Bold').fontSize(12).text('Signature:', { continued: true });
        doc.moveDown(0.5);
        const imgBuffer = Buffer.isBuffer(signatureImage) ? signatureImage : Buffer.from(signatureImage, 'base64');
        doc.image(imgBuffer, doc.x, doc.y, { fit: [180, 60], align: 'left', valign: 'center' });
        doc.moveDown(2);

        // Footer
        doc.font('Helvetica-Oblique').fontSize(10).fillColor('gray')
          .text('This contract is generated electronically and is valid without a physical signature.', 72, 780, {
            align: 'center'
          });

        doc.end();
        pdfBuffer = await streamToBuffer(doc);
        originalFilename = `signed_contract_${userId}_${Date.now()}.pdf`;
        mimeType = 'application/pdf';
        fileSize = pdfBuffer.length;
        logger.info('ContractUploadService.uploadContract:pdf_generated', { userId, filename: originalFilename, fileSize });
      } else if (file) {
        logger.info('ContractUploadService.uploadContract:using_uploaded_pdf', { userId, filename: file.originalname });
        if (file.mimetype !== 'application/pdf') throw new Error('Only PDF files are allowed');
        pdfBuffer = file.buffer;
        originalFilename = file.originalname;
        mimeType = file.mimetype;
        fileSize = file.size;
      } else {
        // NEW: auto-generate PDF from latest active template (contract_type) when no file provided
        const tmpl = await DocumentTemplateService.getLatestActiveForUserType(
          user_type,
          'contract',
          contract_type,
          null,
          resolvedLanguage
        );
        if (!tmpl) {
          throw new Error(`No active ${contract_type} template found for user type ${user_type}`);
        }
        logger.info('ContractUploadService.uploadContract:generate_from_template', { userId, tmplId: tmpl.id, contract_type });
        // Fetch HTML from Exoscale
        const client = exoS3 || new S3Client({ region: process.env.EXOSCALE_REGION, endpoint: process.env.EXOSCALE_ENDPOINT, forcePathStyle: true, credentials: { accessKeyId: process.env.EXOSCALE_ACCESS_KEY, secretAccessKey: process.env.EXOSCALE_SECRET_KEY } });
        const obj = await client.send(new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: tmpl.storageKey }));
        const htmlBuffer = await streamToBuffer(obj.Body);
        let htmlTemplate = htmlBuffer.toString('utf-8');

        // Merge DB-derived vars with request data so placeholders fill like the preview endpoint
        const vars = await buildTemplateVars({ userId, user_type, contractData, unitOfWork });
        Object.entries(vars).forEach(([key, value]) => {
          const re = new RegExp(`{{\s*${key}\s*}}`, 'g');
          htmlTemplate = htmlTemplate.replace(re, String(value ?? ''));
        });
        if (signatureImage) {
          const base64 = String(signatureImage).startsWith('data:') ? String(signatureImage).split(',')[1] : String(signatureImage);
          const tag = `<img src="data:image/png;base64,${base64}" style="max-width:200px;max-height:100px;">`;
          htmlTemplate = htmlTemplate.replace(/{{\s*signatureImage\s*}}/g, tag);
        }

        // Apply Profit Planet signature placeholder (if any)
        htmlTemplate = await applyProfitPlanetSignatureForHtml({ html: htmlTemplate, userId });

        // Render HTML to PDF via Puppeteer (closest to preview output)
        const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox'] });
        const page = await browser.newPage();
        await page.setContent(htmlTemplate, { waitUntil: 'networkidle0' });
        pdfBuffer = await page.pdf({ format: 'A4', printBackground: true });
        await browser.close();

        originalFilename = `${contract_type}_contract_${userId}_${Date.now()}.pdf`;
        mimeType = 'application/pdf';
        fileSize = pdfBuffer.length;
        logger.info('ContractUploadService.uploadContract:pdf_generated_from_template', { userId, contract_type, filename: originalFilename, fileSize });
      }

      // Upload to Exoscale
      logger.info('ContractUploadService.uploadContract:uploading_to_exoscale', { userId, filename: originalFilename });
      const uploadResult = await uploadBuffer(
        pdfBuffer,
        originalFilename,
        mimeType,
        `contracts/${contractCategory}/${userId}/${contract_type}`
      );
      logger.info('ContractUploadService.uploadContract:uploaded', { userId, objectKey: uploadResult.objectKey });

      // Insert metadata in user_documents
      const repo = new UserDocumentRepository(unitOfWork);
      await repo.insertDocument({
        userId,
        documentType,
        contractType: contract_type || 'contract',
        objectStorageId: uploadResult.objectKey,
        signatureBase64: null,
        idType: null,
        idNumber: null,
        expiryDate: null,
        originalFilename,
        fileSize,
        mimeType
      });
      logger.info('ContractUploadService.uploadContract:document_metadata_inserted', { userId });

      // Optionally update user_status (contract_signed)
      await unitOfWork.connection.query(
        `UPDATE user_status SET contract_signed = 1, contract_signed_at = NOW() WHERE user_id = ?`,
        [userId]
      );
      logger.info('ContractUploadService.uploadContract:user_status_updated', { userId });

      // Check if all steps are complete and set status to 'pending' if so
      const UserStatusService = require('../status/UserStatusService');
      await UserStatusService.checkAndSetPendingIfComplete(userId, unitOfWork);
      logger.info('ContractUploadService.uploadContract:pending_check_complete', { userId });

      logger.info('ContractUploadService.uploadContract:success', { userId });
      return uploadResult;
    } catch (error) {
      logger.error('ContractUploadService.uploadContract:error', { userId, error: error.message });
      throw error;
    }
  }
}

// Reuse helper for preview endpoints
ContractUploadService.buildTemplateVars = buildTemplateVars;

module.exports = ContractUploadService;
