"""
file_parser_py — Phase 1 CloudI external service.

Handles complex CSV and XML file parsing that exceeds what the Broadway
adapter_file pipeline handles natively. Simple CSV rows continue via the
existing Broadway pipeline (unchanged). Complex multi-sheet XLSX, XML
with namespaces, and large binary files route here via AdapterCloudi.

Service path: /+/file/parse/  (handles any tenant)

cloudi.conf entry:
  [{prefix,        "/+/file/parse/"},
   {file_path,     "/usr/bin/python3"},
   {args,          "/app/services/file_parser_py/main.py"},
   {count_process, 2},
   {max_r,         5},
   {max_t,         60},
   {env,           [{"PYTHONPATH", "/usr/local/lib/cloudi-2.0.7/api/python"}]}
  ]
"""

import sys
import json
import logging
import base64
import io

sys.path.append('/usr/local/lib/cloudi-2.0.7/api/python/')

from cloudi import API, terminate_exception

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] [CloudI:file_parser_py] %(message)s'
)
logger = logging.getLogger('file_parser_py')


class FileParserService:
    """
    CloudI external service — complex file parsing.

    Handles:
      - CSV with non-standard delimiters, encodings, multi-line fields
      - XML with namespaces (using lxml)
      - Multi-sheet XLSX (using openpyxl)
      - Large files that exceed adapter_file's in-memory Broadway pipeline

    Returns a normalised list of row dicts suitable for MwKernel.Message payload.
    """

    SUPPORTED_FORMATS = {'csv', 'xml', 'xlsx', 'xls'}

    def __init__(self):
        self.__api = API(0)

    def run(self):
        try:
            # Matches /+/file/parse/ — "+" matches any tenant_id
            self.__api.subscribe("+/file/parse/", self.__handle_request)
            logger.info("file_parser_py started, subscribing to +/file/parse/")
            self.__api.poll()
        except terminate_exception:
            logger.info("file_parser_py terminated cleanly")
        except Exception as e:
            logger.error(f"Service error: {e}", exc_info=True)

    def __handle_request(self, request_type, name, pattern,
                         request_info, request,
                         timeout, priority, trans_id, pid):
        try:
            payload = json.loads(request)
            info = json.loads(request_info) if request_info else {}
            trace_id = info.get('trace_id', 'unknown')
            tenant_id = info.get('tenant_id', 'unknown')

            logger.info(f"Parsing file trace_id={trace_id} tenant_id={tenant_id}")

            result = self.__process(payload, info)

            logger.info(f"File parsed successfully trace_id={trace_id} "
                        f"rows={result.get('row_count', 0)}")

            return json.dumps({"status": "ok", "data": result})

        except Exception as e:
            logger.error(f"Parse failed: {e}", exc_info=True)
            return json.dumps({"status": "error", "reason": str(e)})

    def __process(self, payload, info):
        """
        Parse a file payload from MW-Core.

        Expected payload structure:
          {
            "format": "csv" | "xml" | "xlsx",
            "content_base64": "<base64-encoded file bytes>",
            "options": {
              "delimiter": ",",   # CSV only
              "encoding": "utf-8",
              "sheet_name": 0     # XLSX only
            }
          }

        Returns:
          {
            "rows": [...],
            "row_count": N,
            "columns": [...],
            "format": "csv"
          }
        """
        file_format = payload.get('format', '').lower()
        content_b64 = payload.get('content_base64', '')
        options = payload.get('options', {})

        if file_format not in self.SUPPORTED_FORMATS:
            raise ValueError(f"Unsupported format: {file_format}. "
                             f"Supported: {self.SUPPORTED_FORMATS}")

        file_bytes = base64.b64decode(content_b64)

        if file_format == 'csv':
            return self.__parse_csv(file_bytes, options)
        elif file_format == 'xml':
            return self.__parse_xml(file_bytes, options)
        elif file_format in ('xlsx', 'xls'):
            return self.__parse_xlsx(file_bytes, options)

    def __parse_csv(self, file_bytes, options):
        import csv

        encoding = options.get('encoding', 'utf-8')
        delimiter = options.get('delimiter', ',')

        content = file_bytes.decode(encoding, errors='replace')
        reader = csv.DictReader(io.StringIO(content), delimiter=delimiter)
        rows = list(reader)

        return {
            'format': 'csv',
            'rows': rows,
            'row_count': len(rows),
            'columns': list(rows[0].keys()) if rows else []
        }

    def __parse_xml(self, file_bytes, options):
        try:
            from lxml import etree
        except ImportError:
            raise ImportError("lxml required for XML parsing — add to requirements.txt")

        root = etree.fromstring(file_bytes)
        rows = []

        # Normalise XML elements to dicts
        for element in root:
            row = {}
            for child in element:
                # Strip namespace prefix for clean keys
                tag = etree.QName(child.tag).localname
                row[tag] = child.text
            rows.append(row)

        return {
            'format': 'xml',
            'rows': rows,
            'row_count': len(rows),
            'columns': list(rows[0].keys()) if rows else [],
            'root_tag': etree.QName(root.tag).localname
        }

    def __parse_xlsx(self, file_bytes, options):
        try:
            import openpyxl
        except ImportError:
            raise ImportError("openpyxl required for XLSX parsing — add to requirements.txt")

        workbook = openpyxl.load_workbook(io.BytesIO(file_bytes), read_only=True)
        sheet_name = options.get('sheet_name', workbook.sheetnames[0])
        sheet = workbook[sheet_name]

        rows_iter = sheet.iter_rows(values_only=True)
        headers = [str(h) if h is not None else '' for h in next(rows_iter, [])]
        rows = [dict(zip(headers, row)) for row in rows_iter]

        workbook.close()

        return {
            'format': 'xlsx',
            'rows': rows,
            'row_count': len(rows),
            'columns': headers,
            'sheet': sheet_name
        }


if __name__ == '__main__':
    assert API.thread_count() == 1, "Single-threaded service required"
    FileParserService().run()
