import type {
  TemplateDefinition,
  ResolvedTemplate,
  TemplateResolutionOptions,
  TemplateCacheEntry,
  TemplateStore,
  TemplateVariable,
} from '../types/template.types';
import { AllDefaultTemplates, DEFAULT_LOCALE } from '../templates/default-templates';

const CACHE_TTL_MS = 300000;
const VARIABLE_PATTERN = /\{\{(\w+)\}\}/g;
const TRUSTED_DOMAINS = ['shieldai.com', 'shieldai.app', 'app.shieldai.com', 'api.shieldai.com'];

export class TemplateService {
  private static instance: TemplateService;
  private templateStore: TemplateStore;
  private cache: Map<string, TemplateCacheEntry>;
  private customTemplates: Map<string, TemplateDefinition[]>;

  private constructor() {
    this.templateStore = new Map();
    this.cache = new Map();
    this.customTemplates = new Map();
    this.initializeDefaults();
  }

  static getInstance(): TemplateService {
    if (!TemplateService.instance) {
      TemplateService.instance = new TemplateService();
    }
    return TemplateService.instance;
  }

  private initializeDefaults() {
    for (const template of AllDefaultTemplates) {
      const key = this.getStoreKey(template.id, template.locale);
      if (!this.templateStore.has(template.id)) {
        this.templateStore.set(template.id, new Map());
      }
      this.templateStore.get(template.id)!.set(template.locale, template);
    }
  }

  private getStoreKey(templateId: string, locale: string): string {
    return `${templateId}:${locale}`;
  }

  private getCacheKey(templateId: string, locale: string): string {
    return `${templateId}:${locale}`;
  }

  registerTemplate(template: TemplateDefinition): void {
    if (!this.templateStore.has(template.id)) {
      this.templateStore.set(template.id, new Map());
    }
    const localeMap = this.templateStore.get(template.id)!;
    localeMap.set(template.locale, template);
    this.invalidateCache(template.id, template.locale);
  }

  registerTemplates(templates: TemplateDefinition[]): void {
    for (const template of templates) {
      this.registerTemplate(template);
    }
  }

  resolveTemplate(options: TemplateResolutionOptions): ResolvedTemplate | null {
    const { templateId, locale = DEFAULT_LOCALE, variables, fallbackLocale = DEFAULT_LOCALE } = options;

    const cached = this.getCached(templateId, locale);
    if (cached) {
      return this.renderTemplate(cached, variables || {});
    }

    const template = this.findTemplate(templateId, locale, fallbackLocale);
    if (!template) {
      return null;
    }

    this.cacheTemplate(template);
    return this.renderTemplate(template, variables || {});
  }

  private findTemplate(
    templateId: string,
    locale: string,
    fallbackLocale: string
  ): TemplateDefinition | null {
    const localeMap = this.templateStore.get(templateId);

    if (!localeMap) {
      return null;
    }

    const normalizedLocale = this.normalizeLocale(locale);

    if (localeMap.has(normalizedLocale)) {
      return localeMap.get(normalizedLocale)!;
    }

    const languageCode = normalizedLocale.split('-')[0];
    for (const [key, template] of localeMap.entries()) {
      if (key.split('-')[0] === languageCode && key !== normalizedLocale) {
        return template;
      }
    }

    if (localeMap.has(fallbackLocale)) {
      return localeMap.get(fallbackLocale)!;
    }

    for (const [key] of localeMap.entries()) {
      if (key.split('-')[0] === fallbackLocale.split('-')[0]) {
        return localeMap.get(key)!;
      }
    }

    const firstTemplate = localeMap.values().next().value;
    return firstTemplate || null;
  }

  private normalizeLocale(locale: string): string {
    const parts = locale.toLowerCase().split(/[-_]/);
    if (parts.length === 1) {
      return parts[0];
    }
    return parts[0] + '-' + parts[1].toUpperCase();
  }

  private renderTemplate(
    template: TemplateDefinition,
    variables: Record<string, unknown>
  ): ResolvedTemplate {
    const subject = template.subject
      ? this.substituteVariables(template.subject, variables, template.variables)
      : template.subject;

    const body = this.substituteVariables(
      template.body,
      variables,
      template.variables
    );

    const htmlBody = template.htmlBody
      ? this.substituteVariables(template.htmlBody, variables, template.variables)
      : template.htmlBody;

    return {
      id: template.id,
      subject,
      body,
      htmlBody,
      locale: template.locale,
      channel: template.channel,
    };
  }

  private substituteVariables(
    text: string,
    variables: Record<string, unknown>,
    schema: TemplateVariable[]
  ): string {
    const varMap = new Map<string, TemplateVariable>();
    for (const v of schema) {
      varMap.set(v.name, v);
    }

    const isHtmlContext = text.includes('<') && text.includes('>');

return text.replace(VARIABLE_PATTERN, (match, varName) => {
      const value = variables[varName];
      if (value !== undefined) {
        const stringValue = String(value);
        if (this.isUrlVariable(varName)) {
          return this.validateUrl(stringValue);
        }
        return isHtmlContext ? this.escapeHtml(stringValue) : stringValue;
      }
      const schemaVar = varMap.get(varName);
      if (schemaVar?.defaultValue !== undefined) {
        const defaultValue = String(schemaVar.defaultValue);
        if (this.isUrlVariable(varName)) {
          return this.validateUrl(defaultValue);
        }
        return isHtmlContext ? this.escapeHtml(defaultValue) : defaultValue;
      }
      return match;
    });
  }

  private escapeHtml(value: string): string {
    return value
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#039;');
  }

  private validateUrl(url: string): string {
    try {
      const parsed = new URL(url);
      if (TRUSTED_DOMAINS.includes(parsed.hostname)) {
        return url;
      }
      return '/';
    } catch {
      return '/';
    }
  }

  private isUrlVariable(varName: string): boolean {
    return varName.endsWith('_url');
  }

  private getCached(templateId: string, locale: string): TemplateDefinition | null {
    const cacheKey = this.getCacheKey(templateId, locale);
    const entry = this.cache.get(cacheKey);

    if (!entry) {
      return null;
    }

    const age = Date.now() - entry.resolvedAt.getTime();
    if (age > entry.ttl) {
      this.cache.delete(cacheKey);
      return null;
    }

    return entry.template;
  }

  private cacheTemplate(template: TemplateDefinition): void {
    const cacheKey = this.getCacheKey(template.id, template.locale);
    this.cache.set(cacheKey, {
      template,
      resolvedAt: new Date(),
      ttl: CACHE_TTL_MS,
    });
  }

  private invalidateCache(templateId: string, locale: string): void {
    const cacheKey = this.getCacheKey(templateId, locale);
    this.cache.delete(cacheKey);
  }

  getAvailableLocales(templateId: string): string[] {
    const localeMap = this.templateStore.get(templateId);
    if (!localeMap) {
      return [];
    }
    return Array.from(localeMap.keys());
  }

  getTemplateIds(): string[] {
    return Array.from(this.templateStore.keys());
  }

  getTemplateInfo(templateId: string): {
    id: string;
    locales: string[];
    channel: string;
    category: string;
    variables: string[];
  } | null {
    const localeMap = this.templateStore.get(templateId);
    if (!localeMap) {
      return null;
    }
    const firstTemplate = localeMap.values().next().value;
    if (!firstTemplate) {
      return null;
    }
    return {
      id: templateId,
      locales: Array.from(localeMap.keys()),
      channel: firstTemplate.channel,
      category: firstTemplate.category,
      variables: firstTemplate.variables.map(v => v.name),
    };
  }

  clearCache(): void {
    this.cache.clear();
  }

  getCacheStats(): { size: number; totalTemplates: number } {
    return {
      size: this.cache.size,
      totalTemplates: this.templateStore.size,
    };
  }
}
