import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req, Res, UploadedFile, UseInterceptors, Headers, HttpCode } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiTags, ApiBearerAuth, ApiSecurity, ApiConsumes } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { z } from 'zod';
import { ApiUpsertItemsSchema, ApiAsnSchema, ApiOrderSchema, ApiInventoryQuerySchema, CreateWebhookSchema, UpdateWebhookSchema, CreateConnectorSchema, UpdateConnectorSchema, MessageQuerySchema, CreateImportSchema, PaginationQuery } from '@wms/shared';
import { RequirePermissions, ApiKeyScopes, RequireFeature } from '@/common/auth/decorators';
import { zodBody, zodQuery } from '@/common/validation/zod.pipe';
import { PublicApiService } from './public-api.service';
import { WebhooksService } from './webhooks.service';
import { ImportsService } from './imports.service';
import { ErpService } from './erp/erp.service';

const ItemsQuery = z.object({ storerCode: z.string().optional(), code: z.string().optional(), updatedSince: z.string().optional(), page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(500).default(100) });
const StorerQuery = z.object({ storerCode: z.string().optional() });
const SerialQuery = StorerQuery.extend({ itemCode: z.string().optional(), serialNumber: z.string().optional(), status: z.string().optional() });
const CancelSchema = z.object({ reason: z.string().min(1).max(500) });
const AckSchema = z.object({ ids: z.array(z.string().uuid()).min(1) });
const MsgQuery = MessageQuerySchema.merge(PaginationQuery);

/** Public integration API for storers and ERPs. Authenticated with X-API-Key. */
@ApiTags('public-api-v1')
@ApiSecurity('api-key')
@RequireFeature('integrations.api')
@Controller('v1')
export class PublicApiController {
  constructor(
    private readonly api: PublicApiService,
    private readonly erp: ErpService,
  ) {}

  @Put('items') @ApiKeyScopes('items:write') @RequirePermissions('masterdata.manage')
  upsertItems(@Query('storerCode') storerCode: string | undefined, @Body(zodBody(ApiUpsertItemsSchema)) body: z.infer<typeof ApiUpsertItemsSchema>, @Headers('idempotency-key') key?: string) { return this.api.upsertItems(storerCode, body.items, key); }

  @Get('items') @ApiKeyScopes('items:read') @RequirePermissions('masterdata.read')
  listItems(@Query(zodQuery(ItemsQuery)) q: z.infer<typeof ItemsQuery>) { return this.api.listItems(q.storerCode, q); }

  @Post('asns') @ApiKeyScopes('asn:write') @RequirePermissions('inbound.manage')
  createAsn(@Body(zodBody(ApiAsnSchema)) dto: z.infer<typeof ApiAsnSchema>, @Headers('idempotency-key') key?: string) { return this.api.createAsn(dto, key); }

  @Get('asns/:reference') @ApiKeyScopes('asn:read') @RequirePermissions('inbound.read')
  asnStatus(@Param('reference') reference: string, @Query(zodQuery(StorerQuery)) q: z.infer<typeof StorerQuery>) { return this.api.asnStatus(q.storerCode, reference); }

  @Post('orders') @ApiKeyScopes('orders:write') @RequirePermissions('outbound.manage')
  createOrder(@Body(zodBody(ApiOrderSchema)) dto: z.infer<typeof ApiOrderSchema>, @Headers('idempotency-key') key?: string) { return this.api.createOrder(dto, key); }

  @Get('orders/:reference') @ApiKeyScopes('orders:read') @RequirePermissions('outbound.read')
  orderStatus(@Param('reference') reference: string, @Query(zodQuery(StorerQuery)) q: z.infer<typeof StorerQuery>) { return this.api.orderStatus(q.storerCode, reference); }

  @Post('orders/:reference/cancel') @ApiKeyScopes('orders:write') @RequirePermissions('outbound.manage')
  cancelOrder(@Param('reference') reference: string, @Query(zodQuery(StorerQuery)) q: z.infer<typeof StorerQuery>, @Body(zodBody(CancelSchema)) body: z.infer<typeof CancelSchema>) { return this.api.cancelOrder(q.storerCode, reference, body.reason); }

  @Get('inventory') @ApiKeyScopes('inventory:read') @RequirePermissions('inventory.read')
  inventory(@Query(zodQuery(ApiInventoryQuerySchema)) q: z.infer<typeof ApiInventoryQuerySchema>) { return this.api.inventory(q); }

  @Get('serials') @ApiKeyScopes('serials:read') @RequirePermissions('inventory.read')
  serials(@Query(zodQuery(SerialQuery)) q: z.infer<typeof SerialQuery>) { return this.api.serials(q.storerCode, q); }

  /** ERP document intake (e.g. SAP IDoc XML). Body is the raw document. */
  @Post('erp/:connectorId/inbound') @ApiKeyScopes('asn:write', 'orders:write') @RequirePermissions('inbound.manage') @HttpCode(202)
  async erpInbound(@Param('connectorId') connectorId: string, @Req() req: Request, @Headers('idempotency-key') key?: string) {
    const raw = typeof req.body === 'string' ? req.body : Buffer.isBuffer(req.body) ? req.body.toString('utf8') : JSON.stringify(req.body);
    return this.erp.ingest(connectorId, raw, key);
  }

  @Get('erp/outbox') @ApiKeyScopes('asn:read', 'orders:read') @RequirePermissions('inbound.read')
  outbox(@Query('limit') limit?: string) { return this.erp.outbox(limit ? Number(limit) : 50); }

  @Post('erp/outbox/ack') @ApiKeyScopes('asn:read', 'orders:read') @RequirePermissions('inbound.read')
  ack(@Body(zodBody(AckSchema)) body: z.infer<typeof AckSchema>) { return this.erp.ack(body.ids); }
}

/** Tenant-side configuration of integrations (web UI). */
@ApiTags('integrations')
@ApiBearerAuth()
@Controller('integrations')
export class IntegrationsController {
  constructor(
    private readonly webhooks: WebhooksService,
    private readonly imports: ImportsService,
    private readonly erp: ErpService,
  ) {}

  @Get('webhooks') @RequirePermissions('integrations.read') listWebhooks() { return this.webhooks.list(); }
  @Post('webhooks') @RequirePermissions('integrations.manage') @RequireFeature('integrations.webhooks') createWebhook(@Body(zodBody(CreateWebhookSchema)) dto: z.infer<typeof CreateWebhookSchema>) { return this.webhooks.create(dto); }
  @Patch('webhooks/:id') @RequirePermissions('integrations.manage') updateWebhook(@Param('id') id: string, @Body(zodBody(UpdateWebhookSchema)) dto: z.infer<typeof UpdateWebhookSchema>) { return this.webhooks.update(id, dto); }
  @Delete('webhooks/:id') @RequirePermissions('integrations.manage') deleteWebhook(@Param('id') id: string) { return this.webhooks.remove(id); }
  @Get('webhooks/:id/deliveries') @RequirePermissions('integrations.read') deliveries(@Param('id') id: string, @Query(zodQuery(PaginationQuery)) q: z.infer<typeof PaginationQuery>) { return this.webhooks.deliveries(id, q); }
  @Post('deliveries/:id/redeliver') @RequirePermissions('integrations.manage') redeliver(@Param('id') id: string) { return this.webhooks.redeliver(id); }

  @Get('connectors') @RequirePermissions('integrations.read') listConnectors() { return this.webhooks.listConnectors(); }
  @Post('connectors') @RequirePermissions('integrations.manage') createConnector(@Body(zodBody(CreateConnectorSchema)) dto: z.infer<typeof CreateConnectorSchema>) { return this.webhooks.createConnector(dto); }
  @Patch('connectors/:id') @RequirePermissions('integrations.manage') updateConnector(@Param('id') id: string, @Body(zodBody(UpdateConnectorSchema)) dto: z.infer<typeof UpdateConnectorSchema>) { return this.webhooks.updateConnector(id, dto); }

  /** Manual ERP document upload from the UI (same adapter path as the API). */
  @Post('connectors/:id/inbound') @RequirePermissions('integrations.manage') @UseInterceptors(FileInterceptor('file')) @ApiConsumes('multipart/form-data')
  connectorInbound(@Param('id') id: string, @UploadedFile() file: Express.Multer.File) { return this.erp.ingest(id, file.buffer.toString('utf8'), `upload:${Date.now()}`); }

  @Get('messages') @RequirePermissions('integrations.read') messages(@Query(zodQuery(MsgQuery)) q: z.infer<typeof MsgQuery>) { return this.webhooks.messages(q); }
  @Get('messages/:id') @RequirePermissions('integrations.read') message(@Param('id') id: string) { return this.webhooks.message(id); }
  @Post('messages/:id/retry') @RequirePermissions('integrations.manage') retry(@Param('id') id: string) { return this.erp.retry(id); }

  @Get('imports') @RequirePermissions('integrations.read') listImports() { return this.imports.list(); }
  @Get('imports/:id') @RequirePermissions('integrations.read') getImport(@Param('id') id: string) { return this.imports.get(id); }
  @Get('imports/template/:type') @RequirePermissions('integrations.read')
  async template(@Param('type') type: string, @Res() res: Response) {
    const buf = await this.imports.template(type as never);
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    res.setHeader('Content-Disposition', `attachment; filename="import-template-${type.toLowerCase()}.xlsx"`);
    res.send(buf);
  }
  @Post('imports') @RequirePermissions('integrations.manage') @UseInterceptors(FileInterceptor('file')) @ApiConsumes('multipart/form-data')
  runImport(@Body() body: Record<string, string>, @UploadedFile() file: Express.Multer.File) {
    const dto = CreateImportSchema.parse({ ...body, dryRun: body.dryRun === 'true' || body.dryRun === '1' });
    return this.imports.run(dto, file);
  }
}
