import path from "node:path";
import { fileURLToPath } from 'node:url';

import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";

import { config } from "@opengis/fastify-table/utils.js";

const filepath = fileURLToPath(import.meta.url);
const dirname = path.dirname(filepath);

const PROTO_PATH = path.join(dirname, '../', 'map.proto');
// const PROTO_PATH = config.mapServerAddress && config.mapServerAddress.startsWith('127.0.0.1') ? path.join(dirname, '../../../../../../service/mapnikp/map.proto') : path.join(dirname, '../', 'map.proto');

const { mapServerAddress, ready } = config;

if (!mapServerAddress) {
  throw new Error("Map server address is not defined in the configuration.");
}

const proto = grpc.loadPackageDefinition(
  protoLoader.loadSync(
    PROTO_PATH,
    {
      keepCase: true,
      longs: String,
      enums: String,
      defaults: true,
      oneofs: true,
    },
  ),
);

const mapClient = new proto.map.MapService(
  mapServerAddress,
  grpc.credentials.createInsecure(),
);

mapClient.waitForReady(Date.now() + 5000, (err) => {
  if (err) {
    ready.mapnik = false;
    console.error("Mapnik client connection timeout or failure:", err);
  }
  else {
    ready.mapnik = true;
    console.log("Mapnik client connected successfully.");
  }
});

/*
[
  'ClearTile',
  'CreateXML',
  'GetLogs',
  'GetRender',
  'GetStat',
  'ReadDir',
  'RenderTile',
  'UploadRaster',
  'UploadXML',
  'GetRasterStatus', // for resumable upload support
]
*/

function formatObject(obj1) {
  return Object.keys(obj1 || {}).reduce((acc1, key) => {
    const { kind } = obj1[key];
    const value = obj1[key][kind];
    if (key === 'timestamp') {
      Object.assign(acc1, { [key]: new Date(value * 1000).toISOString() });
    }
    else if (kind === 'structValue') {
      Object.assign(acc1, { [key]: formatObject(value.fields) });
    }
    else {
      Object.assign(acc1, { [key]: value });
    }
    return acc1;
  }, {});
}

const grpcMethods = Object.getOwnPropertyNames(
  Object.getPrototypeOf(mapClient),
)
  .filter(key => key.slice(0, 1).toUpperCase() === key.slice(0, 1))
  .reduce((acc, curr) => ({
    ...acc,
    [curr]: async (inputData, callback, unsafe) => new Promise((res, rej) => {
      if (curr === "CreateXML" && typeof callback === 'function') {
        const stream = mapClient[curr].call(
          mapClient,
          inputData,
          (err, data) => {
            if (err) {
              return rej(err);
            }
            return res(data);
          },
        );
        stream.on("error", (err) => {
          // unsafe=true prevents from sequence termination etc
          callback(err?.message || err, unsafe ? null : 1);
          rej(err);
        });
        stream.on("data", (resp) => {
          callback(resp?.message || resp?.error || JSON.stringify(resp));
        });
        stream.on("end", () => res('finish'));
      }
      else if (curr === 'GetStat') {
        mapClient[curr].call(
          mapClient,
          inputData,
          (err, data) => {
            if (err) {
              return rej(err || 'invalid response');
            }

            const { fields } = data?.result || {};
            // google.protobuf.Struct to js object
            const obj = formatObject(fields);
            return res(obj);
          },
        );
      }
      else {
        mapClient[curr].call(
          mapClient,
          inputData,
          (err, data) => {
            if (err) {
              return rej(err);
            }
            return res(data);
          },
        );
      }
    }),
  }), {});

const getMapnik = () => ({
  ...grpcMethods,
  mapServerAddress,
});

export default getMapnik;
