import sanitizeHtml from 'sanitize-html';
import LegalPage, { CONTENT_PAGE_TYPES, CONTENT_PAGE_TITLES, ContentPageType } from '../models/legalPage';

const badRequest = (msg: string): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 400 });

const notFound = (msg: string): Error & { statusCode: number } =>
  Object.assign(new Error(msg), { statusCode: 404 });

const TITLE_MAX_LENGTH = 200;

const COLOR_PATTERNS = [/^#[0-9a-f]{3,8}$/i, /^rgba?\(\s*[\d.%\s,]+\)$/i, /^[a-z]+$/i];

// Content comes from the admin rich text editor and is rendered as HTML for employees,
// so only formatting markup is kept (no scripts, event handlers, iframes, javascript: URLs)
const RICH_TEXT_OPTIONS: sanitizeHtml.IOptions = {
  allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'h1', 'h2', 'span', 'u', 's', 'del', 'ins', 'sub', 'sup']),
  allowedAttributes: {
    a: ['href', 'name', 'target', 'rel'],
    img: ['src', 'alt', 'title', 'width', 'height'],
    td: ['colspan', 'rowspan'],
    th: ['colspan', 'rowspan'],
    ol: ['start', 'type'],
    '*': ['style', 'class'],
  },
  allowedStyles: {
    '*': {
      color: COLOR_PATTERNS,
      'background-color': COLOR_PATTERNS,
      'text-align': [/^(left|right|center|justify)$/],
      'text-decoration': [/^(underline|line-through|none)$/],
      'font-weight': [/^(normal|bold|[1-9]00)$/],
      'font-style': [/^(normal|italic)$/],
      'font-size': [/^\d+(\.\d+)?(px|em|rem|%|pt)$/],
      'padding-left': [/^\d+(\.\d+)?(px|em|rem)$/],
      'margin-left': [/^\d+(\.\d+)?(px|em|rem)$/],
    },
  },
  allowedSchemes: ['http', 'https', 'mailto', 'tel'],
  allowedSchemesByTag: { img: ['http', 'https', 'data'] },
  transformTags: {
    a: sanitizeHtml.simpleTransform('a', { rel: 'noopener noreferrer' }),
  },
};

// Editors send markup like "<p><br></p>" for a blank document
const isEmptyRichText = (html: string): boolean => {
  if (/<img\b/i.test(html)) return false;
  const text = sanitizeHtml(html, { allowedTags: [], allowedAttributes: {} });
  return !text.replace(/&nbsp;| /g, '').trim();
};

const parseContentPageType = (value: unknown): ContentPageType => {
  const type = typeof value === 'string' ? value.trim().toLowerCase() : '';
  if (!CONTENT_PAGE_TYPES.includes(type as ContentPageType)) {
    throw badRequest(`type must be one of ${CONTENT_PAGE_TYPES.join(', ')}`);
  }
  return type as ContentPageType;
};

export const getContentPage = async (typeInput: unknown) => {
  const type = parseContentPageType(typeInput);
  const page = await LegalPage.findOne({ type }).lean();
  if (!page) throw notFound(`${CONTENT_PAGE_TITLES[type]} not found`);
  return page;
};

export const updateContentPage = async (typeInput: unknown, payload: { title?: unknown; content?: unknown }) => {
  const type = parseContentPageType(typeInput);

  if (typeof payload.content !== 'string') throw badRequest('content is required');
  const content = sanitizeHtml(payload.content, RICH_TEXT_OPTIONS).trim();
  if (isEmptyRichText(content)) throw badRequest('content is required');

  let title: string | undefined;
  if (payload.title !== undefined) {
    if (typeof payload.title !== 'string' || !payload.title.trim()) {
      throw badRequest('title must be a non-empty string');
    }
    if (payload.title.trim().length > TITLE_MAX_LENGTH) {
      throw badRequest(`title must be at most ${TITLE_MAX_LENGTH} characters`);
    }
    title = payload.title.trim();
  }

  // Without a title, keep the stored one; fall back to the default only when the page is first created
  const update = title
    ? { $set: { title, content, updatedAt: new Date() } }
    : {
        $set: { content, updatedAt: new Date() },
        $setOnInsert: { title: CONTENT_PAGE_TITLES[type] },
      };

  return LegalPage.findOneAndUpdate({ type }, update, { upsert: true, new: true, runValidators: true }).lean();
};
