import * as interfaces from '../../ts_interfaces/index.js';
import { type IServerOptions, type ISecurityHeaders, type IBundledContentItem, TypedServer } from '../classes.typedserver.js';
import * as plugins from '../plugins.js';

export interface IUtilityWebsiteServerConstructorOptions {
  /** Custom route handler to add additional routes */
  addCustomRoutes?: (typedserver: TypedServer) => Promise<any>;
  /** Application semantic version */
  appSemVer?: string;
  /** Domain name for the website */
  domain: string;
  /** Directory to serve static files from */
  serveDir?: string;
  /** Bundled content to serve from memory (base64-encoded files from tsbundle) */
  bundledContent?: IBundledContentItem[];
  /** RSS feed metadata */
  feedMetadata?: IServerOptions['feedMetadata'];
  /** Enable/disable CORS (default: true) */
  cors?: boolean;
  /** Enable/disable SPA fallback (default: true) */
  spaFallback?: boolean;
  /** Security headers configuration */
  securityHeaders?: ISecurityHeaders;
  /** Force SSL redirect (default: false) */
  forceSsl?: boolean;
  /** Port to listen on (default: 3000) */
  port?: number;
  /** ads.txt entries (only served if configured) */
  adsTxt?: string[];
  /** Response compression configuration (default: enabled with brotli + gzip) */
  compression?: plugins.smartserve.ICompressionConfig | boolean;
  /** Disable browser caching (default: true when serveDir is set) */
  noCache?: boolean;
  /** Inject live-reload devtools script into HTML (default: true when serveDir is set) */
  injectReload?: boolean;
}

/**
 * the utility website server implements a best practice server for websites
 * It supports:
 *  * live reload
 *  * serviceworker
 *  * pwa manifest
 */
export class UtilityWebsiteServer {
  public options: IUtilityWebsiteServerConstructorOptions;
  public typedserver: TypedServer;
  public typedrouter = new plugins.typedrequest.TypedRouter();

  constructor(optionsArg: IUtilityWebsiteServerConstructorOptions) {
    this.options = optionsArg;
  }

  /**
   * Start the website server
   */
  public async start(portArg?: number) {
    const port = portArg ?? this.options.port ?? 3000;

    this.typedserver = new TypedServer({
      // Core settings
      cors: this.options.cors ?? true,
      serveDir: this.options.serveDir,
      bundledContent: this.options.bundledContent,
      domain: this.options.domain,
      port,

      // Development features
      injectReload: this.options.injectReload ?? true,
      watch: !!this.options.serveDir,
      noCache: this.options.noCache ?? true,

      // SPA support (enabled by default for modern web apps)
      spaFallback: this.options.spaFallback ?? true,

      // Security
      forceSsl: this.options.forceSsl ?? false,
      securityHeaders: this.options.securityHeaders,

      // Compression
      compression: this.options.compression,

      // PWA manifest
      manifest: {
        name: this.options.domain,
        short_name: this.options.domain,
        start_url: '/',
        display_override: ['window-controls-overlay'],
        lang: 'en',
        background_color: '#000000',
        scope: '/',
      },

      // SEO features
      robots: true,
      sitemap: true,
      feedMetadata: this.options.feedMetadata,
    });

    let lswData: interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo['response'] = {
      appHash: 'xxxxxx',
      appSemVer: this.options.appSemVer || 'x.x.x',
    };

    // -> Service worker version info handler
    this.typedserver.typedrouter.addTypedHandler<interfaces.serviceworker.IRequest_Serviceworker_Backend_VersionInfo>(
      new plugins.typedrequest.TypedHandler('serviceworker_versionInfo', async () => {
        return lswData;
      })
    );

    // ads.txt handler (only if configured)
    if (this.options.adsTxt && this.options.adsTxt.length > 0) {
      this.typedserver.addRoute('/ads.txt', 'GET', async () => {
        const adsTxt = this.options.adsTxt.join('\n') + '\n';
        return new Response(adsTxt, {
          status: 200,
          headers: { 'Content-Type': 'text/plain' },
        });
      });
    }

    // Asset broker manifest handler
    this.typedserver.addRoute(
      '/assetbroker/manifest/:manifestAsset',
      'GET',
      async (ctx) => {
        let manifestAssetName = ctx.params?.manifestAsset;
        if (manifestAssetName === 'favicon.png') {
          manifestAssetName = `favicon_${this.options.domain
            .replace('.', '')
            .replace('losslesscom', 'lossless')}@2x_transparent.png`;
        }
        const fullOriginAssetUrl = `https://assetbroker.lossless.one/brandfiles/00general/${manifestAssetName}`;
        console.log(`Getting ${manifestAssetName} from ${fullOriginAssetUrl}`);
        const smartRequest = plugins.smartrequest.SmartRequest.create();
        const response = await smartRequest.url(fullOriginAssetUrl).get();
        const arrayBuffer = await response.arrayBuffer();
        return new Response(arrayBuffer, {
          status: 200,
          headers: { 'Content-Type': 'image/png' },
        });
      }
    );

    // Add any custom routes
    if (this.options.addCustomRoutes) {
      await this.options.addCustomRoutes(this.typedserver);
    }

    // Subscribe to serve directory hash changes
    this.typedserver.serveDirHashSubject.subscribe((appHash: string) => {
      lswData = {
        appHash,
        appSemVer: '1.0.0',
      };
    });

    // Setup the typedrouter chain
    this.typedserver.typedrouter.addTypedRouter(this.typedrouter);

    // Start everything
    console.log('routes are all set. Starting up now!');
    await this.typedserver.start();
    console.log('typedserver started!');
  }

  public async stop() {
    await this.typedserver.stop();
  }
}
