#!/usr/bin/env node
/**
 * Module Generator Script
 *
 * Generates the standard platform-service module structure:
 * types.ts → repository.ts → routes.ts → test.ts
 *
 * Usage:
 *   pnpm gen:module --name tasks --fields "title:string,status:enum(pending,active,done),priority:number" --partition userId
 *
 * @module platform-service/scripts/gen-module
 */

import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

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

// Parse CLI arguments
function parseArgs() {
  const args = process.argv.slice(2);
  const options = {
    name: '',
    fields: '',
    partition: 'id',
    dryRun: false,
  };

  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === '--name' || arg === '-n') {
      options.name = args[++i];
    } else if (arg === '--fields' || arg === '-f') {
      options.fields = args[++i];
    } else if (arg === '--partition' || arg === '-p') {
      options.partition = args[++i];
    } else if (arg === '--dry-run' || arg === '-d') {
      options.dryRun = true;
    } else if (arg === '--help' || arg === '-h') {
      showHelp();
      process.exit(0);
    }
  }

  if (!options.name || !options.fields) {
    console.error('Error: --name and --fields are required');
    showHelp();
    process.exit(1);
  }

  return options;
}

function showHelp() {
  console.log(`
Module Generator — Create platform-service module scaffolding

Usage:
  pnpm gen:module --name <name> --fields "<field:type,...>" --partition <key> [--dry-run]

Options:
  --name, -n         Module name (e.g., "tasks", "notifications")
  --fields, -f       Field definitions (e.g., "title:string,status:enum(pending,done)")
  --partition, -p    Partition key (default: "id")
  --dry-run, -d      Preview output without writing files
  --help, -h         Show this help

Field Types:
  string              String field
  number              Numeric field
  boolean             Boolean field
  date                Date field (ISO string)
  datetime            DateTime field (ISO string with time)
  enum(a,b,c)         Enum with values a, b, c
  string[]            Array of strings
  number[]            Array of numbers

Examples:
  pnpm gen:module --name tasks --fields "title:string,status:enum(pending,active,done),priority:number?" --partition userId
  pnpm gen:module --name alerts --fields "message:string,level:enum(info,warn,error),metadata:object" --partition userId
`);
}

// Parse field definitions
function parseFields(fieldsStr: string): Array<{
  name: string;
  type: string;
  optional: boolean;
  zodType: string;
  tsType: string;
  enumValues: string[] | null;
}> {
  const fields = [];
  const fieldDefs = fieldsStr.split(',').map(f => f.trim()).filter(Boolean);

  for (const def of fieldDefs) {
    const match = def.match(/^([a-zA-Z_][a-zA-Z0-9_]*):([a-z]+(?:\([^)]+\))?|\[?\w+\]?)\??$/);
    if (!match) {
      throw new Error(`Invalid field definition: ${def}`);
    }

    const [, name, typeSpec] = match;
    const optional = def.endsWith('?');

    let type = typeSpec.replace('?', '');
    let zodType = '';
    let tsType = '';
    let enumValues = null;

    if (type.startsWith('enum(')) {
      enumValues = type.slice(5, -1).split('|').map((v: string) => v.trim());
      zodType = `z.enum([${enumValues.map(v => `"${v}"`).join(', ')}])`;
      tsType = enumValues.map(v => `"${v}"`).join(' | ');
    } else if (type === 'string') {
      zodType = 'z.string()';
      tsType = 'string';
    } else if (type === 'number') {
      zodType = 'z.number()';
      tsType = 'number';
    } else if (type === 'boolean') {
      zodType = 'z.boolean()';
      tsType = 'boolean';
    } else if (type === 'date' || type === 'datetime') {
      zodType = 'z.string().datetime()';
      tsType = 'string';
    } else if (type === 'string[]') {
      zodType = 'z.array(z.string())';
      tsType = 'string[]';
    } else if (type === 'number[]') {
      zodType = 'z.array(z.number())';
      tsType = 'number[]';
    } else {
      zodType = 'z.any()';
      tsType = 'unknown';
    }

    fields.push({
      name,
      type,
      optional,
      zodType,
      tsType,
      enumValues,
    });
  }

  return fields;
}

// Generate types.ts content
function generateTypes(name: string, fields: ReturnType<typeof parseFields>, partitionKey: string) {
  const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
  const fieldSchemas = fields.map((f: ReturnType<typeof parseFields>[0]) =>
    `  ${f.name}: ${f.zodType}${f.optional ? '.optional()' : ''}`
  ).join(',\n');

  const interfaceFields = fields.map((f: ReturnType<typeof parseFields>[0]) =>
    `  ${f.name}${f.optional ? '?' : ''}: ${f.tsType};`
  ).join('\n');

  return `/**
 * ${pascalName} Types
 *
 * @module platform-service/src/modules/${name}/types
 */

import { z } from 'zod';

// ── MongoDB Document ID ─────────────────────────────────────
export const ObjectIdSchema = z.string().regex(/^[0-9a-fA-F]{24}$/, {
  message: 'Invalid ObjectId format',
});

// ── Core ${pascalName} Schema ─────────────────────────────────────
export const ${pascalName}Schema = z.object({
  id: ObjectIdSchema,
  productId: z.string(),
  ${partitionKey}: z.string(),
${fieldSchemas},
  createdAt: z.string().datetime(),
  updatedAt: z.string().datetime(),
});

export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;

// ── Create Input ────────────────────────────────────────────
export const Create${pascalName}Schema = z.object({
${fieldSchemas},
});

export type Create${pascalName}Input = z.infer<typeof Create${pascalName}Schema>;

// ── Update Input ────────────────────────────────────────────
export const Update${pascalName}Schema = z.object({
${fields.filter(f => f.name !== partitionKey).map(f => `  ${f.name}: ${f.zodType}.optional()`).join(',\n')},
});

export type Update${pascalName}Input = z.infer<typeof Update${pascalName}Schema>;

// ── Query Params ─────────────────────────────────────────────
export const Query${pascalName}Schema = z.object({
  ${partitionKey}: z.string().optional(),
  limit: z.number().min(1).max(100).default(20).optional(),
  offset: z.number().min(0).default(0).optional(),
});

export type Query${pascalName}Params = z.infer<typeof Query${pascalName}Schema>;
`;
}

// Generate repository.ts content
function generateRepository(name: string, fields: ReturnType<typeof parseFields>, partitionKey: string) {
  const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
  const camelName = name.charAt(0).toLowerCase() + name.slice(1);

  return `/**
 * ${pascalName} Repository
 *
 * @module platform-service/src/modules/${name}/repository
 */

import { getContainer } from '../../lib/cosmos.js';
import {
  type ${pascalName},
  type Create${pascalName}Input,
  type Update${pascalName}Input,
  type Query${pascalName}Params,
} from './types.js';

const CONTAINER_NAME = '${camelName}';

// ── Create ────────────────────────────────────────────────────
export async function create${pascalName}(
  productId: string,
  input: Create${pascalName}Input & { ${partitionKey}: string }
): Promise<${pascalName}> {
  const container = await getContainer(CONTAINER_NAME);

  const doc = {
    id: crypto.randomUUID(),
    productId,
    ...input,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
  };

  const { resource } = await container.items.create(doc);
  return resource as ${pascalName};
}

// ── Read ─────────────────────────────────────────────────────
export async function get${pascalName}(
  id: string,
  productId: string
): Promise<${pascalName} | null> {
  const container = await getContainer(CONTAINER_NAME);

  try {
    const { resource } = await container.item(id, productId).read();
    return resource as ${pascalName} | null;
  } catch {
    return null;
  }
}

// ── List ────────────────────────────────────────────────────
export async function list${pascalName}s(
  productId: string,
  params: Query${pascalName}Params
): Promise<{ items: ${pascalName}[]; total: number }> {
  const container = await getContainer(CONTAINER_NAME);

  const query = {
    query: 'SELECT * FROM c WHERE c.productId = @productId ORDER BY c.createdAt DESC',
    parameters: [{ name: '@productId', value: productId }],
  };

  const { resources: items } = await container.items.query(query).fetchAll();

  const countQuery = {
    query: 'SELECT VALUE COUNT(1) FROM c WHERE c.productId = @productId',
    parameters: [{ name: '@productId', value: productId }],
  };
  const { resources: [total] } = await container.items.query(countQuery).fetchAll();

  return { items: items as ${pascalName}[], total: total as number };
}

// ── Update ──────────────────────────────────────────────────
export async function update${pascalName}(
  id: string,
  productId: string,
  input: Update${pascalName}Input
): Promise<${pascalName} | null> {
  const container = await getContainer(CONTAINER_NAME);

  const existing = await get${pascalName}(id, productId);
  if (!existing) return null;

  const doc = {
    ...existing,
    ...input,
    updatedAt: new Date().toISOString(),
  };

  const { resource } = await container.items.upsert(doc);
  return resource as ${pascalName};
}

// ── Delete ────────────────────────────────────────────────────
export async function delete${pascalName}(
  id: string,
  productId: string
): Promise<boolean> {
  const container = await getContainer(CONTAINER_NAME);

  try {
    await container.item(id, productId).delete();
    return true;
  } catch {
    return false;
  }
}
`;
}

// Generate routes.ts content
function generateRoutes(name: string, fields: ReturnType<typeof parseFields>, partitionKey: string) {
  const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
  const camelName = name.charAt(0).toLowerCase() + name.slice(1);

  return `/**
 * ${pascalName} Routes
 *
 * @module platform-service/src/modules/${name}/routes
 */

import type { FastifyInstance } from 'fastify';
import { NotFoundError, BadRequestError } from '../../lib/errors.js';
import { getRequestProductId } from '../../lib/request-context.js';
import {
  Create${pascalName}Schema,
  Update${pascalName}Schema,
  Query${pascalName}Schema,
} from './types.js';
import {
  create${pascalName},
  get${pascalName},
  list${pascalName}s,
  update${pascalName},
  delete${pascalName},
} from './repository.js';

export async function ${camelName}Routes(app: FastifyInstance) {
  // List ${camelName}s
  app.get('/', async (req) => {
    const productId = getRequestProductId(req);
    const params = Query${pascalName}Schema.parse(req.query);
    return list${pascalName}s(productId, params);
  });

  // Get ${camelName} by ID
  app.get<{ Params: { id: string } }>('/:id', async (req) => {
    const productId = getRequestProductId(req);
    const item = await get${pascalName}(req.params.id, productId);
    if (!item) throw new NotFoundError('${pascalName} not found');
    return item;
  });

  // Create ${camelName}
  app.post('/', async (req) => {
    const productId = getRequestProductId(req);
    const body = Create${pascalName}Schema.parse(req.body);
    return create${pascalName}(productId, { ...body, ${partitionKey}: req.user?.id || 'unknown' });
  });

  // Update ${camelName}
  app.patch<{ Params: { id: string } }>('/:id', async (req) => {
    const productId = getRequestProductId(req);
    const body = Update${pascalName}Schema.parse(req.body);
    const item = await update${pascalName}(req.params.id, productId, body);
    if (!item) throw new NotFoundError('${pascalName} not found');
    return item;
  });

  // Delete ${camelName}
  app.delete<{ Params: { id: string } }>('/:id', async (req, reply) => {
    const productId = getRequestProductId(req);
    const deleted = await delete${pascalName}(req.params.id, productId);
    if (!deleted) throw new NotFoundError('${pascalName} not found');
    reply.status(204);
  });
}
`;
}

// Generate test.ts content
function generateTest(name: string, fields: ReturnType<typeof parseFields>, partitionKey: string) {
  const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
  const camelName = name.charAt(0).toLowerCase() + name.slice(1);

  return `/**
 * ${pascalName} Module Tests
 *
 * @module platform-service/src/modules/${name}/${name}.test
 */

import { describe, it, expect, beforeAll } from 'vitest';
import { createTestApp } from '../../test-helpers.js';
import {
  Create${pascalName}Schema,
  Update${pascalName}Schema,
} from './types.js';

describe('${pascalName} module', () => {
  const app = createTestApp();

  describe('Create${pascalName}Schema', () => {
    it('accepts valid input', () => {
${fields.filter(f => !f.optional).map(f => `      const result = Create${pascalName}Schema.parse({
        ${f.name}: ${f.type === 'string' ? "'test'" : f.type === 'number' ? '123' : f.type === 'boolean' ? 'true' : "'value'"}
      });
      expect(result).toBeDefined();`).join('\n')}
    });

    it('rejects invalid input', () => {
      expect(() => Create${pascalName}Schema.parse({})).toThrow();
    });
  });

  describe('Update${pascalName}Schema', () => {
    it('accepts partial updates', () => {
      const result = Update${pascalName}Schema.parse({});
      expect(result).toEqual({});
    });
  });

  describe('API Routes', () => {
    it('POST /api/${camelName} creates item', async () => {
      const res = await app.inject({
        method: 'POST',
        url: '/api/${camelName}',
        payload: {
${fields.filter(f => !f.optional).slice(0, 2).map(f => `          ${f.name}: ${f.type === 'string' ? "'Test ${f.name}'" : f.type === 'number' ? '42' : 'true'}`).join(',\n')}
        },
      });
      expect(res.statusCode).toBe(201);
    });

    it('GET /api/${camelName} lists items', async () => {
      const res = await app.inject({
        method: 'GET',
        url: '/api/${camelName}',
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.payload);
      expect(body.items).toBeDefined();
      expect(body.total).toBeDefined();
    });
  });
});
`;
}

// Main generator function
async function generateModule() {
  const options = parseArgs();
  const { name, fields, partition, dryRun } = options;

  console.log(`\\n🚀 Generating module: ${name}`);
  console.log(`   Fields: ${fields}`);
  console.log(`   Partition: ${partition}`);
  if (dryRun) console.log('   ⚠️  DRY RUN - No files will be written');

  try {
    const parsedFields = parseFields(fields);
    console.log(`   Parsed ${parsedFields.length} fields`);

    const files = {
      'types.ts': generateTypes(name, parsedFields, partition),
      'repository.ts': generateRepository(name, parsedFields, partition),
      'routes.ts': generateRoutes(name, parsedFields, partition),
      [`${name}.test.ts`]: generateTest(name, parsedFields, partition),
    };

    if (dryRun) {
      console.log('\\n📄 Generated files (preview):');
      for (const [filename, content] of Object.entries(files)) {
        console.log(`\\n--- ${filename} ---`);
        console.log(content.slice(0, 500) + (content.length > 500 ? '\\n...' : ''));
      }
    } else {
      // Create module directory
      const moduleDir = path.join(__dirname, '..', 'src', 'modules', name);
      await fs.mkdir(moduleDir, { recursive: true });

      // Write files
      for (const [filename, content] of Object.entries(files)) {
        const filepath = path.join(moduleDir, filename);
        await fs.writeFile(filepath, content, 'utf-8');
        console.log(`   ✅ ${filename}`);
      }

      console.log(`\\n✨ Module created: ${moduleDir}`);
      console.log(`\\nNext steps:`);
      console.log(`   1. Add container to cosmos-init.ts: { name: '${name}', partitionKey: '/${partition}' }`);
      console.log(`   2. Import and register routes in server.ts`);
      console.log(`   3. Run tests: pnpm --filter @lysnrai/platform-service test`);
    }

  } catch (error) {
    console.error('\\n❌ Error:', error instanceof Error ? error.message : String(error));
    process.exit(1);
  }
}

generateModule();
