import { BDS_PYTHON_API_CONTRACT_V1 } from './pythonApiContractV1';

const PYTHON_RESERVED_KEYWORDS = new Set([
  'false',
  'none',
  'true',
  'and',
  'as',
  'assert',
  'async',
  'await',
  'break',
  'class',
  'continue',
  'def',
  'del',
  'elif',
  'else',
  'except',
  'finally',
  'for',
  'from',
  'global',
  'if',
  'import',
  'in',
  'is',
  'lambda',
  'nonlocal',
  'not',
  'or',
  'pass',
  'raise',
  'return',
  'try',
  'while',
  'with',
  'yield',
  'match',
  'case',
]);

function toSnakeCase(value: string): string {
  return value
    .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
    .replace(/[^a-zA-Z0-9]+/g, '_')
    .replace(/^_+|_+$/g, '')
    .toLowerCase();
}

function quotePython(value: string): string {
  return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}

function toPythonIdentifier(value: string): string {
  let identifier = toSnakeCase(value);
  if (!identifier) {
    identifier = '_';
  }

  if (/^[0-9]/.test(identifier)) {
    identifier = `_${identifier}`;
  }

  if (PYTHON_RESERVED_KEYWORDS.has(identifier)) {
    identifier = `${identifier}_`;
  }

  return identifier;
}

function buildPythonMethod(method: {
  method: string;
  description: string;
  params: Array<{ name: string; required: boolean }>;
}): string {
  const [namespace, member] = method.method.split('.');
  if (!namespace || !member) {
    return '';
  }

  const pythonMethodName = toPythonIdentifier(member);
  const pythonParams = method.params.map((param) => ({
    sourceName: param.name,
    pythonName: toPythonIdentifier(param.name),
    required: param.required,
  }));

  const signature = pythonParams.length > 0
    ? `, ${pythonParams.map((param) => (param.required ? param.pythonName : `${param.pythonName}=None`)).join(', ')}`
    : '';

  const argsDict = method.params.length > 0
    ? `{ ${method.params.map((param, index) => `"${param.name}": ${pythonParams[index]?.pythonName}`).join(', ')} }`
    : '{}';

  return [
    `    async def ${pythonMethodName}(self${signature}):`,
    `        \"\"\"${quotePython(method.description)}\"\"\"`,
    `        return await self._transport.call("${method.method}", ${argsDict})`,
    '',
  ].join('\n');
}

function buildPythonNamespaceClass(
  namespace: string,
  methods: Array<{ method: string; description: string; params: Array<{ name: string; required: boolean }> }>
): string {
  const className = `${namespace[0].toUpperCase()}${namespace.slice(1)}Api`;
  const methodBlocks = methods.map((method) => buildPythonMethod(method)).join('');

  return [
    `class ${className}:`,
    '    def __init__(self, transport):',
    '        self._transport = transport',
    '',
    methodBlocks.trimEnd(),
    '',
  ].join('\n');
}

export function generatePythonApiModuleV1(): string {
  const namespaceMap = new Map<string, Array<{ method: string; description: string; params: Array<{ name: string; required: boolean }> }>>();

  for (const method of BDS_PYTHON_API_CONTRACT_V1.methods) {
    const [namespace] = method.method.split('.');
    if (!namespace) {
      continue;
    }

    const entries = namespaceMap.get(namespace) ?? [];
    entries.push({
      method: method.method,
      description: method.description,
      params: method.params.map((param) => ({
        name: param.name,
        required: param.required,
      })),
    });
    namespaceMap.set(namespace, entries);
  }

  const namespaceBlocks = Array.from(namespaceMap.entries())
    .sort(([left], [right]) => left.localeCompare(right))
    .map(([namespace, methods]) => buildPythonNamespaceClass(namespace, methods))
    .join('\n');

  const namespaceAssignments = Array.from(namespaceMap.keys())
    .sort((left, right) => left.localeCompare(right))
    .map((namespace) => `        self.${toPythonIdentifier(namespace)} = ${namespace[0].toUpperCase()}${namespace.slice(1)}Api(transport)`)
    .join('\n');

  return [
    '# Auto-generated by generatePythonApiModuleV1.ts',
    `# Contract version: ${BDS_PYTHON_API_CONTRACT_V1.version}`,
    '',
    'import json',
    '',
    'class BdsApiError(Exception):',
    '    pass',
    '',
    namespaceBlocks.trimEnd(),
    '',
    'class BdsApi:',
    '    def __init__(self, transport):',
    '        self._transport = transport',
    namespaceAssignments,
    '',
    'class _Transport:',
    '    def __init__(self, call_impl):',
    '        self._call_impl = call_impl',
    '',
    '    async def call(self, method, args):',
    '        raw_result = await self._call_impl(method, json.dumps(args))',
    '        if raw_result is None or raw_result == "":',
    '            return None',
    '        return json.loads(raw_result)',
    '',
    'def install_bds_api(call_impl):',
    '    _transport = _Transport(call_impl)',
    '    bds = BdsApi(_transport)',
    '    return bds',
    '',
  ].join('\n');
}