/**
 * QA-1: Stateful In-Memory-Mock der GerbilManagerWebAPI per Route-Interception.
 *
 * Bedient den DATA-2-Vertrag: Gridify-paged Listen ({items, totalCount, page,
 * pageSize}), camelCase-DTOs, 409 Conflict bei referenzierten Becken/Kontakten
 * (von Dwight bestätigt). Jeder Test bekommt über die Fixture in fixtures.ts
 * einen FRISCHEN Datenbestand — CRUD-Flüsse mutieren ihn wie das echte Backend.
 */
import type { Page, Route } from '@playwright/test'
import { seedDb, type MockDb } from './mock-data'

/** Default-API-Origin des Frontends (src/api/client.ts). */
const API_ORIGIN = 'http://localhost:5179'

type Row = Record<string, unknown>

let seq = 0
const newId = (prefix: string) => `${prefix}-e2e-${++seq}`

/** Gridify-Escapes entfernen (\, vor Sonderzeichen). */
const unescapeGridify = (s: string) => s.replace(/\\(.)/g, '$1')

/** Mini-Gridify: genau die Ausdrücke, die die App baut (=, contains, ',', '|').
 * Gleichheit akzeptiert '=' (kanonisch) UND '==' (alt) — POLISH-1 stellte den
 * Client auf Gridifys echtes '=' um; der Mock bleibt für beide robust. */
function matchesFilter(row: Row, filter: string | null): boolean {
  if (!filter) return true
  return filter.split(',').every((andPart) =>
    andPart.split('|').some((cond) => {
      // Gridify "contains": `field=*value` — value runs to the end (optionally /i).
      // Mirror the REAL backend: no trailing `*` (the old `=\*(.*)\*` regex hid the
      // SQL-LIKE `name=*a*` bug that returned 0 rows against real Gridify).
      let m = cond.match(/^(\w+)=\*(.*?)(\/i)?$/)
      if (m) {
        return String(row[m[1]] ?? '')
          .toLowerCase()
          .includes(unescapeGridify(m[2]).toLowerCase())
      }
      m = cond.match(/^(\w+)==?(.*)$/)
      if (m) return String(row[m[1]] ?? '') === unescapeGridify(m[2])
      return true
    }),
  )
}

function applyOrderBy(rows: Row[], orderBy: string | null): Row[] {
  if (!orderBy) return rows
  const [field, dir] = orderBy.split(/\s+/)
  const sign = dir?.toLowerCase() === 'desc' ? -1 : 1
  return [...rows].sort((a, b) => {
    const av = String(a[field] ?? '')
    const bv = String(b[field] ?? '')
    return av < bv ? -sign : av > bv ? sign : 0
  })
}

function pagedResponse(rows: Row[], url: URL) {
  const filtered = rows.filter((r) => matchesFilter(r, url.searchParams.get('filter')))
  const sorted = applyOrderBy(filtered, url.searchParams.get('orderBy'))
  const page = Number(url.searchParams.get('page') ?? '1')
  const pageSize = Number(url.searchParams.get('pageSize') ?? '20')
  return {
    items: sorted.slice((page - 1) * pageSize, page * pageSize),
    totalCount: filtered.length,
    page,
    pageSize,
  }
}

const json = (route: Route, status: number, body?: unknown) =>
  route.fulfill({
    status,
    contentType: 'application/json; charset=utf-8',
    body: body === undefined ? '' : JSON.stringify(body),
  })

/**
 * Generische CRUD-Kollektion. `deleteConflict` bildet die 409-Regel des
 * Backends ab (Becken nicht leer / Kontakt referenziert).
 */
function collection(
  rows: Row[],
  idPrefix: string,
  deleteConflict?: (id: string, db: MockDb) => boolean,
) {
  return { rows, idPrefix, deleteConflict }
}

export async function installMockApi(page: Page): Promise<MockDb> {
  const db = seedDb()

  const collections: Record<string, ReturnType<typeof collection>> = {
    gerbils: collection(db.gerbils as unknown as Row[], 'gerbil'),
    litters: collection(db.litters as unknown as Row[], 'litter'),
    enclosures: collection(db.enclosures as unknown as Row[], 'enc', (id, d) =>
      d.gerbils.some((g) => g.enclosureId === id),
    ),
    contacts: collection(db.contacts as unknown as Row[], 'con', (id, d) =>
      d.gerbils.some((g) => g.originContactId === id || g.receiverContactId === id),
    ),
    'color-varieties': collection(db.colorVarieties as unknown as Row[], 'cv'),
    'health-records': collection(db.healthRecords as unknown as Row[], 'hr'),
    'weight-records': collection(db.weightRecords as unknown as Row[], 'wr'),
    contracts: collection(db.contracts as unknown as Row[], 'contract'),
  }

  const handler = async (route: Route) => {
    const request = route.request()
    const url = new URL(request.url())
    const method = request.method()
    // OPS-1: in Produktion ist die API-Basis der relative Pfad /api (nginx-Proxy);
    // den Präfix normalisieren, damit der Mock unter beiden Basen funktioniert.
    const path = url.pathname.replace(/^\/api(?=\/)/, '')

    // EXPORT-1: Zip-Download (Inhalt egal — der Smoke prüft nur, dass der
    // Download startet; ein leeres Zip = End-of-central-directory-Record).
    if (path === '/export' && method === 'GET') {
      return route.fulfill({
        status: 200,
        contentType: 'application/zip',
        headers: { 'Content-Disposition': 'attachment; filename="rennmaus-export-e2e.zip"' },
        body: Buffer.from([0x50, 0x4b, 0x05, 0x06, ...new Array(18).fill(0)]),
      })
    }

    // FEAT-13: Zuchtprofil (Einstellungen-Seite lädt es vor dem Rendern)
    if (path === '/settings/breeder-profile') {
      if (method === 'GET') {
        return json(route, 200, {
          zuchtName: 'Zucht der kleinen Chaoten',
          name: 'Frau Erika Muster',
          address: 'Musterweg 1, 12345 Musterstadt',
          phone: '', email: '', homepage: '', city: 'Musterstadt',
        })
      }
      if (method === 'PUT') return json(route, 204)
    }

    // ── WEB-3: lokale Vorschau (Renderer-Dateien; nur veröffentlichte Seiten) ──
    if (path === '/render-site' && method === 'GET') {
      const files = db.pages
        .filter((p) => p.status === 'Published')
        .map((p) => ({ path: p.slug === 'start' ? 'index.html' : `${p.slug}/index.html`, size: 1000 }))
      return json(route, 200, [{ path: 'assets/site.css', size: 500 }, ...files])
    }
    const pv = path.match(/^\/preview(?:\/(.*))?$/)
    if (pv && method === 'GET') {
      const key = pv[1] ? pv[1].replace(/\/$/, '') : 'index.html'
      if (key === 'assets/site.css') {
        return route.fulfill({ status: 200, contentType: 'text/css', body: 'body{font-family:sans-serif}' })
      }
      const slug = key === 'index.html' ? 'start' : key.replace(/\/index\.html$/, '')
      const p = db.pages.find((x) => x.slug === slug && x.status === 'Published')
      if (!p) return json(route, 404, { title: 'Not Found' })
      return route.fulfill({
        status: 200,
        contentType: 'text/html; charset=utf-8',
        body: `<!doctype html><html lang="de"><head><meta charset="utf-8"><title>${p.title}</title></head><body><h1>${p.title}</h1><p>Vorschau-Mock</p></body></html>`,
      })
    }

    // ── INBOX-1: Anfragen-Posteingang (vor den generischen Kollektionen) ──
    if (path === '/requests/sync' && method === 'POST') {
      return json(route, 200, db.mailConfigured ? { imported: 0, error: null } : { imported: 0, error: 'MailNotConfigured' })
    }
    let rm = path.match(/^\/requests\/([^/]+)\/draft$/)
    if (rm && method === 'POST') {
      if (!db.aiConfigured) {
        return json(route, 503, { code: 'AiKeyMissing', message: 'AI nicht konfiguriert' })
      }
      const r = db.requests.find((x) => x.id === rm![1])
      if (!r) return json(route, 404, { title: 'Not Found' })
      r.draftReply = `Hallo ${r.fromName ?? ''},\n\nvielen Dank für deine Anfrage!\n\nViele Grüße`
      return json(route, 200, r)
    }
    rm = path.match(/^\/requests\/([^/]+)\/send$/)
    if (rm && method === 'POST') {
      if (!db.mailConfigured) {
        return json(route, 503, { title: 'MailNotConfigured', detail: 'Gmail ist noch nicht konfiguriert.' })
      }
      const r = db.requests.find((x) => x.id === rm![1])
      if (!r) return json(route, 404, { title: 'Not Found' })
      r.status = 'Answered'
      r.answeredAt = '2026-06-06T10:00:00Z'
      return json(route, 200, r)
    }
    rm = path.match(/^\/requests(?:\/([^/]+))?$/)
    if (rm) {
      const reqId = rm[1] ? decodeURIComponent(rm[1]) : null
      if (!reqId && method === 'GET') {
        return json(route, 200, pagedResponse(db.requests as unknown as Row[], url))
      }
      const r = db.requests.find((x) => x.id === reqId)
      if (!r) return json(route, 404, { title: 'Not Found' })
      if (method === 'GET') return json(route, 200, r)
      if (method === 'PUT') {
        // Triage-Vertrag des Backends: assignedContactId wird IMMER übernommen.
        const body = request.postDataJSON() as { status?: string | null; assignedContactId?: string | null }
        r.assignedContactId = body.assignedContactId ?? null
        if (body.status) {
          r.status = body.status as typeof r.status
          if (body.status === 'Answered') r.answeredAt ??= '2026-06-06T10:00:00Z'
        }
        return json(route, 204)
      }
      return json(route, 405)
    }

    // ABGABE: KI-Inserat-Generator (POST /gerbils/sale-ad) — vor der generischen Route
    if (path === '/gerbils/sale-ad' && method === 'POST') {
      if (!db.saleAdConfigured) {
        return json(route, 503, { code: 'AiKeyMissing', message: 'AI nicht konfiguriert' })
      }
      return json(route, 200, { text: 'Status: FREI\n\n„Zwei Freunde suchen ein Zuhause" – Generierter Inserat-Text für den Mock.' })
    }

    // ABGABE: Vertrag-Download — /contracts/{id}/file (3 Segmente, nicht vom Generic-Handler bedient)
    const cm = path.match(/^\/contracts\/([^/]+)\/file$/)
    if (cm && method === 'GET') {
      return route.fulfill({
        status: 200,
        contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        headers: { 'Content-Disposition': `attachment; filename="vertrag-${cm[1]}.docx"` },
        body: Buffer.from([0x50, 0x4b, 0x05, 0x06, ...new Array(18).fill(0)]),
      })
    }

    // SEARCH-2b: distinct Herkunft (originBreeder) values, sorted — vor der
    // generischen /gerbils/:id-Route abfangen.
    if (path === '/gerbils/breeders' && method === 'GET') {
      const values = [
        ...new Set(
          db.gerbils
            .map((g) => g.originBreeder)
            .filter((v): v is string => typeof v === 'string' && v.length > 0),
        ),
      ].sort()
      return json(route, 200, values)
    }

    // Sonderrouten zuerst (FEAT-1b/FEAT-4-Verträge)
    let m = path.match(/^\/gerbils\/([^/]+)\/photos$/)
    if (m) {
      if (method === 'GET') return json(route, 200, [])
      return json(route, 405)
    }
    if (path.match(/^\/photos\/[^/]+$/) && method === 'DELETE') return json(route, 204)
    m = path.match(/^\/gerbils\/([^/]+)\/inbreeding-coefficient$/)
    if (m) {
      const isKruemel = m[1] === 'kruemel'
      return json(route, 200, {
        coefficient: isKruemel ? 0.0625 : 0,
        percent: isKruemel ? 6.25 : 0,
        generationsAvailable: isKruemel ? 5 : 2,
        commonAncestors: isKruemel
          ? [{ id: 'anton', name: 'Anton', contribution: 0.0625 }]
          : [],
      })
    }

    // WEB-0b: CMS-Endpunkte (Seiten + Blöcke). Vor den generischen Kollektionen,
    // weil 'pages'/'blocks' verschachtelte Routen haben und kein Standard-CRUD sind.
    const toSummary = (p: (typeof db.pages)[number]) => ({
      id: p.id, slug: p.slug, title: p.title, seoDescription: p.seoDescription, status: p.status,
    })
    const toBlock = (b: { id: string; order: number; type: string; data: unknown }) => ({
      id: b.id, order: b.order, type: b.type, data: b.data,
    })
    const toPage = (p: (typeof db.pages)[number]) => ({
      ...toSummary(p),
      blocks: [...p.blocks].sort((a, b) => a.order - b.order).map(toBlock),
    })

    if (path === '/pages' && method === 'GET') {
      return json(route, 200, [...db.pages].sort((a, b) => a.slug.localeCompare(b.slug)).map(toSummary))
    }
    // GET /pages/{slug}  ·  PUT /pages/{id}
    m = path.match(/^\/pages\/([^/]+)$/)
    if (m && method === 'GET') {
      const p = db.pages.find((x) => x.slug === decodeURIComponent(m![1]))
      return p ? json(route, 200, toPage(p)) : json(route, 404, { title: 'Not Found' })
    }
    // PUT /pages/{id}
    if (m && method === 'PUT') {
      const p = db.pages.find((x) => x.id === m![1])
      if (!p) return json(route, 404, { title: 'Not Found' })
      const body = request.postDataJSON() as Partial<typeof p>
      if (typeof body.slug === 'string') p.slug = body.slug
      if (typeof body.title === 'string') p.title = body.title
      p.seoDescription = (body.seoDescription as string | null) ?? null
      if (body.status === 'Draft' || body.status === 'Published') p.status = body.status
      return json(route, 204)
    }
    // POST /pages/{pageId}/blocks
    m = path.match(/^\/pages\/([^/]+)\/blocks$/)
    if (m && method === 'POST') {
      const p = db.pages.find((x) => x.id === m![1])
      if (!p) return json(route, 404, { title: 'Not Found' })
      const body = request.postDataJSON() as { type: string; data?: Record<string, unknown>; order?: number }
      const order = body.order ?? (p.blocks.reduce((mx, b) => Math.max(mx, b.order), -1) + 1)
      const block = { id: newId('blk'), order, type: body.type, data: body.data ?? {} }
      p.blocks.push(block)
      return json(route, 201, toBlock(block))
    }
    // PUT /pages/{pageId}/blocks/order  (Body: { blockIds })
    m = path.match(/^\/pages\/([^/]+)\/blocks\/order$/)
    if (m && method === 'PUT') {
      const p = db.pages.find((x) => x.id === m![1])
      if (!p) return json(route, 404, { title: 'Not Found' })
      const { blockIds } = request.postDataJSON() as { blockIds: string[] }
      const byId = new Map(p.blocks.map((b) => [b.id, b]))
      if (blockIds.length !== p.blocks.length || blockIds.some((id) => !byId.has(id)))
        return json(route, 400, 'blockIds must list exactly the page block ids.')
      blockIds.forEach((id, i) => { byId.get(id)!.order = i })
      return json(route, 204)
    }
    // PUT/DELETE /blocks/{id}
    m = path.match(/^\/blocks\/([^/]+)$/)
    if (m) {
      const pageOf = db.pages.find((p) => p.blocks.some((b) => b.id === m![1]))
      const block = pageOf?.blocks.find((b) => b.id === m![1])
      if (!pageOf || !block) return json(route, 404, { title: 'Not Found' })
      if (method === 'PUT') {
        const body = request.postDataJSON() as { type?: string; data?: Record<string, unknown>; order?: number }
        if (typeof body.type === 'string') block.type = body.type
        if (body.data) block.data = body.data
        if (typeof body.order === 'number') block.order = body.order
        return json(route, 204)
      }
      if (method === 'DELETE') {
        pageOf.blocks = pageOf.blocks.filter((b) => b.id !== m![1])
        return json(route, 204)
      }
    }

    // ABGABE: POST /contracts — baut url-Feld und markiert Tiere als Abgegeben
    if (path === '/contracts' && method === 'POST') {
      const body = request.postDataJSON() as { contactId: string; gerbilIds: string[]; price: number; handoverDate: string; contractDate?: string | null }
      const id = newId('contract')
      const contract = {
        id,
        contactId: body.contactId,
        gerbilIds: body.gerbilIds ?? [],
        price: body.price,
        handoverDate: body.handoverDate,
        contractDate: body.contractDate ?? body.handoverDate,
        fileName: `vertrag-${id}.docx`,
        createdAt: new Date().toISOString(),
        url: `/contracts/${id}/file`,
      }
      ;(db.contracts as unknown as Row[]).push(contract as unknown as Row)
      // Tiere auf GivenAway setzen (wie das echte Backend)
      for (const gid of body.gerbilIds ?? []) {
        const g = db.gerbils.find((x) => x.id === gid)
        if (g) {
          g.status = 'GivenAway'
          g.receiverContactId = body.contactId
          g.goHomeDate = body.handoverDate
        }
      }
      return json(route, 201, contract)
    }

    // FEAT-NAMEGEN: /names/suggest
    if (path === '/names/suggest' && method === 'GET') {
      if (!db.namesConfigured) {
        return json(route, 503, { code: 'NamesKeyMissing', message: 'Kein API-Key konfiguriert' })
      }
      const letter = url.searchParams.get('letter')?.toUpperCase()
      const allSuggestions = [
        { name: 'Fenrir', meaning: 'Wolf aus der Nordischen Mythologie', origin: 'Nordisch' },
        { name: 'Freya', meaning: 'Göttin der Liebe und Fruchtbarkeit', origin: 'Nordisch' },
        { name: 'Artemis', meaning: 'Göttin der Jagd und des Mondlichts', origin: 'Griech. Mythologie' },
        { name: 'Kira', meaning: 'Strahlendes Licht', origin: 'Japanisch' },
        { name: 'Luna', meaning: 'Mondgöttin', origin: 'Griech. Mythologie' },
        { name: 'Baldur', meaning: 'Gott des Lichts und der Reinheit', origin: 'Nordisch' },
      ]
      const result = letter
        ? allSuggestions.filter((s) => s.name.startsWith(letter))
        : allSuggestions
      return json(route, 200, result)
    }

    // Generische Kollektionen: /<resource> und /<resource>/<id>
    m = path.match(/^\/([a-z-]+)(?:\/([^/]+))?$/)
    const col = m ? collections[m[1]] : undefined
    if (!m || !col) return json(route, 404, { title: 'Not Found' })
    const id = m[2] ? decodeURIComponent(m[2]) : null

    if (!id) {
      if (method === 'GET') return json(route, 200, pagedResponse(col.rows, url))
      if (method === 'POST') {
        const body = request.postDataJSON() as Row
        const created = { id: newId(col.idPrefix), ...body }
        col.rows.push(created)
        return json(route, 201, created)
      }
      return json(route, 405)
    }

    const idx = col.rows.findIndex((r) => r.id === id)
    if (method === 'GET') {
      return idx >= 0 ? json(route, 200, col.rows[idx]) : json(route, 404, { title: 'Not Found' })
    }
    if (method === 'PUT') {
      if (idx < 0) return json(route, 404, { title: 'Not Found' })
      Object.assign(col.rows[idx], request.postDataJSON() as Row)
      return json(route, 200, col.rows[idx])
    }
    if (method === 'DELETE') {
      if (idx < 0) return json(route, 404, { title: 'Not Found' })
      if (col.deleteConflict?.(id, db)) return json(route, 409, { title: 'Conflict' })
      col.rows.splice(idx, 1)
      return json(route, 204)
    }
    return json(route, 405)
  }

  // Beide API-Basen abfangen: absolute Dev-URL und /api-relativ (OPS-1-Proxy).
  // URL-Prädikat statt Glob: '**/api/**' würde auch Vites Modul-Requests
  // (/src/api/client.ts …) treffen und die App selbst kaputt-intercepten.
  await page.route(
    (url) => url.origin === API_ORIGIN || url.pathname.startsWith('/api/'),
    handler,
  )

  return db
}
