import fs from 'node:fs'
import path from 'node:path'
import crypto from 'node:crypto'

const sourcePath = 'contracts/api/kbx.api.json'
const sourceText = fs.readFileSync(sourcePath, 'utf8')
const source = JSON.parse(sourceText)
const sha = crypto.createHash('sha256').update(sourceText).digest('hex')
const operations = source.operations ?? []

const ids = new Set()
const routes = new Set()
for (const op of operations) {
  if (!op.id || !op.method || !op.path) throw new Error(`Invalid API operation: ${JSON.stringify(op)}`)
  if (ids.has(op.id)) throw new Error(`Duplicate operation id: ${op.id}`)
  ids.add(op.id)
  const routeKey = `${op.method} ${op.path}`
  if (routes.has(routeKey)) throw new Error(`Duplicate API route: ${routeKey}`)
  routes.add(routeKey)
  if (!['GET','POST','PUT','PATCH','DELETE'].includes(op.method)) throw new Error(`Unsupported method: ${op.method}`)
  if (op.idempotency === 'required' && op.method === 'GET') throw new Error(`GET cannot require idempotency: ${op.id}`)
}

const manifest = {
  schemaVersion: source.schemaVersion,
  apiVersion: source.apiVersion,
  sourceSha256: sha,
  operationCount: operations.length,
  operations: operations.map(({ source: _source, ...op }) => op),
}
fs.mkdirSync('generated', { recursive: true })
fs.writeFileSync('generated/api-manifest.json', JSON.stringify(manifest, null, 2) + '\n')

const problemSchemaText = fs.readFileSync('contracts/problems/kbx.problem.schema.json','utf8')
const problemSchemaSha = crypto.createHash('sha256').update(problemSchemaText).digest('hex')
const problemSchema = JSON.parse(problemSchemaText)
const problemTypes = ['validation','businessRule','conflict','permission','notFound','integration','system']
  .map(key => problemSchema.$defs?.[key]?.allOf?.[1]?.properties?.type?.const)
  .filter(Boolean)
fs.writeFileSync('generated/problem-manifest.json', JSON.stringify({
  schemaVersion: '1.0', sourceSha256: problemSchemaSha, types: problemTypes,
}, null, 2) + '\n')

const tsOps = operations.map(op => `  ${JSON.stringify(op.id)}: ${JSON.stringify({
  id: op.id, method: op.method, path: op.path, permission: op.permission ?? null, kind: op.kind,
  idempotency: op.idempotency, successStatuses: op.successStatuses ?? [200],
  ...(op.contentType ? { contentType: op.contentType } : {}),
  ...(op.responseType ? { responseType: op.responseType } : {}),
})},`).join('\n')
const ts = `// Generated from ${sourcePath}. Do not edit.\nimport type { KbxApiOperationDefinition } from '../api'\n\nexport const kbxApiSourceSha256 = '${sha}' as const\nexport const kbxApiCatalog = {\n${tsOps}\n} as const satisfies Record<string, KbxApiOperationDefinition>\n\nexport type KbxApiOperationId = keyof typeof kbxApiCatalog\nexport type KbxApiOperation = (typeof kbxApiCatalog)[KbxApiOperationId]\n`
fs.mkdirSync('packages/kbx-contracts/src/generated', { recursive: true })
fs.writeFileSync('packages/kbx-contracts/src/generated/apiCatalog.ts', ts)

const csItems = operations.map(op => `        new(${JSON.stringify(op.id)}, ${JSON.stringify(op.method)}, ${JSON.stringify(op.path)}, ${op.permission ? JSON.stringify(op.permission) : 'null'}, ${JSON.stringify(op.kind)}, ${JSON.stringify(op.idempotency)}, new[] { ${(op.successStatuses ?? [200]).join(', ')} })`).join(',\n')
const cs = `// Generated from ${sourcePath}. Do not edit.\nnamespace Shared.Contracts.Generated;\n\npublic sealed record KbxApiOperationContract(\n    string Id, string Method, string Path, string? Permission, string Kind, string Idempotency, IReadOnlyList<int> SuccessStatuses);\n\npublic static class KbxApiCatalog\n{\n    public const string SourceSha256 = "${sha}";\n    public static readonly IReadOnlyList<KbxApiOperationContract> All = new KbxApiOperationContract[]\n    {\n${csItems}\n    };\n}\n`
fs.mkdirSync('backend/Shared/Contracts/Generated', { recursive: true })
fs.writeFileSync('backend/Shared/Contracts/Generated/KbxApiCatalog.g.cs', cs)

const paths = {}
for (const op of operations) {
  const normalizedPath = op.path.replace(/\{([^}:]+):[^}]+\}/g, '{$1}')
  const method = op.method.toLowerCase()
  paths[normalizedPath] ??= {}
  const parameters = [...normalizedPath.matchAll(/\{([^}]+)\}/g)].map(m => ({
    name: m[1], in: 'path', required: true, schema: { type: 'string' }
  }))
  paths[normalizedPath][method] = {
    operationId: op.id,
    tags: [op.id.split('.')[0].toUpperCase()],
    parameters,
    responses: Object.fromEntries([
      ...(op.successStatuses ?? [200]).map(status => [String(status), { description: 'Success' }]),
      ['400', { description: 'Validation problem', content: { 'application/json': { schema: { '$ref': '#/components/schemas/KbxProblem' } } } }],
      ['403', { description: 'Permission problem', content: { 'application/json': { schema: { '$ref': '#/components/schemas/KbxProblem' } } } }],
      ['404', { description: 'Not found problem', content: { 'application/json': { schema: { '$ref': '#/components/schemas/KbxProblem' } } } }],
      ['409', { description: 'Conflict problem', content: { 'application/json': { schema: { '$ref': '#/components/schemas/KbxProblem' } } } }],
      ['422', { description: 'Business problem', content: { 'application/json': { schema: { '$ref': '#/components/schemas/KbxProblem' } } } }],
      ['500', { description: 'System problem', content: { 'application/json': { schema: { '$ref': '#/components/schemas/KbxProblem' } } } }],
    ]),
    'x-kbx-permission': op.permission ?? null,
    'x-kbx-idempotency': op.idempotency,
    'x-kbx-kind': op.kind,
  }
}
const openapi = {
  openapi: '3.1.0',
  info: { title: 'KBX API Contract', version: source.apiVersion },
  paths,
  components: { schemas: { KbxProblem: problemSchema } },
  'x-kbx-source-sha256': sha,
}
fs.writeFileSync('contracts/api/openapi.kbx.json', JSON.stringify(openapi, null, 2) + '\n')
console.log(`API contracts generated: ${operations.length} operations, SHA ${sha.slice(0,12)}`)
