/**
 * Copyright (C) 2025 Brainformatik GmbH (info@brainformatik.com)
 *
 * This file is part of brainX.
 * All Rights Reserved.
 *
 * This part of brainX can not be copied and/or distributed without the
 * express permission of Brainformatik GmbH.
 */

import { buildMockDataset, MODULE_IDS } from './mockData.js';

const jsonResponse = (payload) => ({
  status: 200,
  headers: {
    'content-type': 'application/json'
  },
  body: JSON.stringify(payload)
});

export class MockApi {
  constructor(context) {
    this.context = context;
    this.dataset = buildMockDataset();
    this.routes = [];

    this.handleMetadata = this.handleMetadata.bind(this);
    this.handleModuleFields = this.handleModuleFields.bind(this);
    this.handleModuleRecords = this.handleModuleRecords.bind(this);
    this.handleRecordDetail = this.handleRecordDetail.bind(this);
    this.handleRelations = this.handleRelations.bind(this);
    this.handleUserProfile = this.handleUserProfile.bind(this);
    this.handleAuth = this.handleAuth.bind(this);
    this.handleTotp = this.handleTotp.bind(this);
    this.handleRefresh = this.handleRefresh.bind(this);
    this.handleLogout = this.handleLogout.bind(this);
  }

  async attachRoute(url, handler) {
    await this.context.route(url, handler);
    this.routes.push({
      url,
      handler 
    });
  }

  async attach() {
    const bases = this.resolveApiBases();
    for (const base of bases) {
      await this.attachBaseRoutes(base);
    }

    const authPatterns = ['**/auth', '**/auth?*'];
    for (const pattern of authPatterns) {
      await this.attachRoute(pattern, this.handleAuth);
    }
    await this.attachRoute('**/auth/code', this.handleTotp);
    await this.attachRoute('**/auth/code?*', this.handleTotp);
    await this.attachRoute('**/auth/refresh', this.handleRefresh);
    await this.attachRoute('**/auth/refresh?*', this.handleRefresh);
    await this.attachRoute('**/auth/logout', this.handleLogout);
    await this.attachRoute('**/auth/logout?*', this.handleLogout);

    await this.attachRoute('**/metadata/entities', this.handleMetadata);
    await this.attachRoute('**/metadata/entities?*', this.handleMetadata);
    await this.attachRoute('**/metadata/entities/*', this.handleModuleFields);
    await this.attachRoute('**/metadata/entities/*?*', this.handleModuleFields);
    await this.attachRoute('**/entity/*/records/*', this.handleRecordDetail);
    await this.attachRoute('**/entity/*/records/*?*', this.handleRecordDetail);
    await this.attachRoute('**/entity/*/records', this.handleModuleRecords);
    await this.attachRoute('**/entity/*/records?*', this.handleModuleRecords);
    await this.attachRoute('**/users/current', this.handleUserProfile);
    await this.attachRoute('**/relations/*', this.handleRelations);
    await this.attachRoute('**/relations/*?*', this.handleRelations);

    await this.attachRoute('**/brainx-main/api/**', this.handleFallback);
    await this.attachRoute('**/api/**', this.handleFallback);
  }

  async dispose() {
    await Promise.all(
      this.routes.map(({
        url, handler 
      }) => this.context.unroute(url, handler))
    );
    this.routes = [];
  }

  setModuleRecords(moduleName, records = []) {
    const moduleId = MODULE_IDS[moduleName] ?? moduleName;
    this.dataset.moduleRecords[moduleId] = records;
  }

  async handleMetadata(route) {
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: this.dataset.metadata
      })
    );
  }

  async handleModuleFields(route) {
    const moduleId = this.extractModuleId(route.request().url(), /\/metadata\/entities\/(\d+)/);
    const fields = this.dataset.moduleFields[moduleId] || [];
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: {
          fields
        }
      })
    );
  }

  async handleModuleRecords(route) {
    const moduleId = this.extractModuleId(route.request().url(), /\/entity\/(\d+)\/records/);
    const records = this.dataset.moduleRecords[moduleId] || [];
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: records,
        metadata: {
          total: records.length,
          actual: records.length
        }
      })
    );
  }

  async handleRecordDetail(route) {
    const match = /\/entity\/(\d+)\/records\/([^/?]+)/.exec(route.request().url());
    if (!match) {
      return route.continue();
    }
    const moduleId = Number(match[1]);
    const recordId = match[2];
    const records = this.dataset.moduleRecords[moduleId] || [];
    const record = records.find((entry) => String(entry.recordId ?? entry.id) === recordId);
    if (!record) {
      await route.fulfill({
        status: 404,
        headers: {
          'content-type': 'application/json' 
        },
        body: JSON.stringify({
          status: 404,
          message: 'Not found' 
        })
      });
      return;
    }
    if (route.request().method() === 'PATCH') {
      const payload = await this.readJsonBody(route);
      Object.assign(record, payload);
      const attachments = payload?.attachments_block_dummy ?? payload?.attachments ?? null;
      if (Array.isArray(attachments)) {
        const normalized = attachments.map((entry, index) => ({
          id: entry?.attachmentsid ?? entry?.attachmentId ?? entry?.id ?? `att-${recordId}-${index + 1}`,
          name: entry?.name ?? `Attachment ${index + 1}`
        }));
        record.attachments_block_dummy = normalized;
        record.attachments = normalized;
      }
      await route.fulfill(
        jsonResponse({
          status: 200,
          message: 'OK',
          data: record,
          metadata: {
            editable: true
          }
        })
      );
      return;
    }
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: record,
        metadata: {
          editable: true
        }
      })
    );
  }

  async handleRelations(route) {
    const match = /\/relations\/([^/?]+)/.exec(route.request().url());
    if (!match) {
      return route.continue();
    }
    const recordId = match[1];
    const entries = this.dataset.recordRelations?.[recordId] || [];
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: entries
      })
    );
  }

  async handleUserProfile(route) {
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: this.dataset.profile
      })
    );
  }

  async handleFallback(route) {
    if (route.request().method() === 'OPTIONS') {
      await route.fulfill({
        status: 204,
        headers: {
          'access-control-allow-origin': '*',
          'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
          'access-control-allow-headers': '*'
        }
      });
      return;
    }
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: {}
      })
    );
  }

  async handleAuth(route) {
    if (route.request().method() === 'OPTIONS') {
      await route.fulfill({
        status: 204,
        headers: {
          'access-control-allow-origin': '*',
          'access-control-allow-methods': 'POST, OPTIONS',
          'access-control-allow-headers': 'content-type'
        }
      });
      return;
    }
    if (route.request().method() !== 'POST') {
      return route.continue();
    }
    const payload = await this.readJsonBody(route);
    const {
      user, password 
    } = payload || {};
    if (user === 'admin' && password === 'admin') {
      await route.fulfill(
        jsonResponse({
          status: 200,
          message: 'OK',
          data: this.buildTokenResponse()
        })
      );
      return;
    }
    await route.fulfill({
      status: 401,
      headers: {
        'content-type': 'application/json' 
      },
      body: JSON.stringify({
        status: 401,
        message: 'Unauthorized.' 
      })
    });
  }

  async handleTotp(route) {
    if (route.request().method() === 'OPTIONS') {
      await route.fulfill({
        status: 204,
        headers: {
          'access-control-allow-origin': '*',
          'access-control-allow-methods': 'POST, OPTIONS',
          'access-control-allow-headers': 'content-type'
        }
      });
      return;
    }
    if (route.request().method() !== 'POST') {
      return route.continue();
    }
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: this.buildTokenResponse()
      })
    );
  }

  async handleRefresh(route) {
    if (route.request().method() === 'OPTIONS') {
      await route.fulfill({
        status: 204,
        headers: {
          'access-control-allow-origin': '*',
          'access-control-allow-methods': 'POST, OPTIONS',
          'access-control-allow-headers': 'content-type'
        }
      });
      return;
    }
    if (route.request().method() !== 'POST') {
      return route.continue();
    }
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK',
        data: this.buildTokenResponse()
      })
    );
  }

  async handleLogout(route) {
    if (route.request().method() === 'OPTIONS') {
      await route.fulfill({
        status: 204,
        headers: {
          'access-control-allow-origin': '*',
          'access-control-allow-methods': 'DELETE, OPTIONS',
          'access-control-allow-headers': 'content-type, authorization'
        }
      });
      return;
    }
    if (route.request().method() !== 'DELETE') {
      return route.continue();
    }
    await route.fulfill(
      jsonResponse({
        status: 200,
        message: 'OK'
      })
    );
  }

  buildTokenResponse() {
    return {
      accessToken: 'mock-access-token',
      refreshToken: 'mock-refresh-token'
    };
  }

  async readJsonBody(route) {
    try {
      return await route.request().postDataJSON();
    } catch {
      return {};
    }
  }

  extractModuleId(url, pattern) {
    const match = url.match(pattern);
    if (!match) {
      return null;
    }
    return Number(match[1]);
  }

  resolveApiBases() {
    const envBases = [
      process.env.NUXT_PUBLIC_PROXY_BASE,
      process.env.NUXT_PUBLIC_API_BASE
    ].filter(Boolean);

    if (!envBases.length) {
      envBases.push('https://jdzaack.tunnel.brain-app.de/brainx-main/api');
    }

    return Array.from(
      new Set(
        envBases
          .map((value) => {
            try {
              const url = new URL(value);
              return url.toString().replace(/\/+$/, '');
            } catch {
              return value.replace(/\/+$/, '');
            }
          })
          .filter(Boolean)
      )
    );
  }

  async attachBaseRoutes(base) {
    const escapedBase = base.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');

    await this.attachRoute(`${base}/auth`, this.handleAuth);
    await this.attachRoute(`${base}/auth?*`, this.handleAuth);
    await this.attachRoute(`${base}/auth/code`, this.handleTotp);
    await this.attachRoute(`${base}/auth/code?*`, this.handleTotp);
    await this.attachRoute(`${base}/auth/refresh`, this.handleRefresh);
    await this.attachRoute(`${base}/auth/refresh?*`, this.handleRefresh);
    await this.attachRoute(`${base}/auth/logout`, this.handleLogout);
    await this.attachRoute(`${base}/auth/logout?*`, this.handleLogout);

    await this.attachRoute(`${base}/metadata/entities`, this.handleMetadata);
    await this.attachRoute(`${base}/metadata/entities?*`, this.handleMetadata);
    await this.attachRoute(new RegExp(`${escapedBase}/metadata/entities/\\d+(?:\\?.*)?$`), this.handleModuleFields);

    await this.attachRoute(new RegExp(`${escapedBase}/entity/\\d+/records(?:\\?.*)?$`), this.handleModuleRecords);
    await this.attachRoute(`${base}/users/current`, this.handleUserProfile);
  }
}
