import axios from 'axios';
import httpClient from '../config/httpClient';
import toolConfigService, { BookstackConfig } from './toolConfig.service';
import logger from '../utils/logger';

export interface BookStackPage {
  id: number;
  name: string;
  slug: string;
  book_id: number;
  book_slug: string;
  chapter_id: number;
  draft: boolean;
  template: boolean;
  created_at: string;
  updated_at: string;
  url: string;
  preview_html?: { content: string };
  book?: { name: string };
  tags?: { name: string; value: string; order: number }[];
  createdBy?: { name: string };
  updatedBy?: { name: string };
}

export interface BookStackSearchResult {
  id: number;
  name: string;
  slug: string;
  book_id: number;
  book_slug: string;
  url: string;
  preview_html: { content: string };
  tags: { name: string; value: string; order: number }[];
}

/**
 * Validates that a URL is safe to use as an outbound service endpoint.
 * Rejects non-http(s) protocols and private/loopback IP ranges to prevent SSRF.
 */
function isValidServiceUrl(raw: string): boolean {
  let parsed: URL;
  try {
    parsed = new URL(raw);
  } catch {
    return false;
  }

  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    return false;
  }

  const hostname = parsed.hostname.toLowerCase();

  // Reject plain loopback / localhost names
  if (hostname === 'localhost' || hostname === '::1') {
    return false;
  }

  // Reject numeric IPv4 private / loopback / link-local ranges
  const ipv4Parts = hostname.split('.');
  if (ipv4Parts.length === 4) {
    const [a, b] = ipv4Parts.map(Number);
    if (
      a === 127 ||                         // 127.0.0.0/8  loopback
      a === 10 ||                          // 10.0.0.0/8   private
      (a === 172 && b >= 16 && b <= 31) || // 172.16.0.0/12 private
      (a === 192 && b === 168) ||          // 192.168.0.0/16 private
      (a === 169 && b === 254)             // 169.254.0.0/16 link-local
    ) {
      return false;
    }
  }

  return true;
}

function buildHeaders(config: BookstackConfig): Record<string, string> {
  return {
    'Authorization': `Token ${config.tokenId}:${config.tokenSecret}`,
    'Content-Type': 'application/json',
  };
}

/**
 * Fetches all BookStack books and returns a map of book_id → book_slug.
 * The /api/pages list endpoint does not reliably include book_slug, so we
 * look it up separately and use it when constructing page URLs.
 * Cached for 5 minutes to avoid hammering the API on every dashboard load.
 */
let bookSlugMapCache: { map: Map<number, string>; expiresAt: number } | null = null;
const BOOK_SLUG_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

async function getBookSlugMap(): Promise<Map<number, string>> {
  if (bookSlugMapCache && Date.now() < bookSlugMapCache.expiresAt) {
    return bookSlugMapCache.map;
  }
  const config = await toolConfigService.getBookstackConfig();
  try {
    const response = await httpClient.get(
      `${config.url}/api/books`,
      { params: { count: 500 }, headers: buildHeaders(config) },
    );
    const books: Array<{ id: number; slug: string }> = response.data?.data ?? [];
    const map = new Map(books.map((b) => [b.id, b.slug]));
    bookSlugMapCache = { map, expiresAt: Date.now() + BOOK_SLUG_CACHE_TTL_MS };
    return map;
  } catch {
    return bookSlugMapCache?.map ?? new Map();
  }
}

async function getRecentPages(): Promise<BookStackPage[]> {
  const config = await toolConfigService.getBookstackConfig();
  if (!config.url || !isValidServiceUrl(config.url)) {
    throw new Error('BOOKSTACK_URL is not configured or is not a valid service URL');
  }

  try {
    const [response, bookSlugMap] = await Promise.all([
      httpClient.get(
        `${config.url}/api/pages`,
        {
          params: { sort: '-updated_at', count: 20 },
          headers: buildHeaders(config),
        },
      ),
      getBookSlugMap(),
    ]);
    const pages: BookStackPage[] = response.data?.data ?? [];
    return pages.map((p) => ({
      ...p,
      url: `${config.url}/books/${bookSlugMap.get(p.book_id) || p.book_slug || p.book_id}/page/${p.slug}`,
    }));
  } catch (error) {
    if (axios.isAxiosError(error)) {
      logger.error('BookStack getRecentPages failed', {
        status: error.response?.status,
        statusText: error.response?.statusText,
      });
    }
    logger.error('BookStackService.getRecentPages failed', { error });
    throw new Error('Failed to fetch BookStack recent pages');
  }
}

async function searchPages(query: string): Promise<BookStackSearchResult[]> {
  const config = await toolConfigService.getBookstackConfig();
  if (!config.url || !isValidServiceUrl(config.url)) {
    throw new Error('BOOKSTACK_URL is not configured or is not a valid service URL');
  }

  try {
    const response = await httpClient.get(
      `${config.url}/api/search`,
      {
        params: { query, count: 50 },
        headers: buildHeaders(config),
      },
    );
    const bookSlugMap = await getBookSlugMap();
    const results: BookStackSearchResult[] = (response.data?.data ?? [])
      .filter((item: any) => item.type === 'page')
      .map((item: any) => ({
        id: item.id,
        name: item.name,
        slug: item.slug,
        book_id: item.book_id ?? 0,
        book_slug: item.book_slug ?? '',
        url: `${config.url}/books/${bookSlugMap.get(item.book_id) || item.book_slug || item.book_id}/page/${item.slug}`,
        preview_html: item.preview_html ?? { content: '' },
        tags: item.tags ?? [],
      }));
    return results;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      logger.error('BookStack searchPages failed', {
        status: error.response?.status,
        statusText: error.response?.statusText,
      });
    }
    logger.error('BookStackService.searchPages failed', { error });
    throw new Error('Failed to search BookStack pages');
  }
}

export interface BookStackPageDetail {
  id: number;
  name: string;
  slug: string;
  book_id: number;
  book_slug: string;
  chapter_id: number;
  html: string;
  created_at: string;
  updated_at: string;
  url: string;
  book?: { name: string };
  createdBy?: { name: string };
  updatedBy?: { name: string };
}

async function getPageById(id: number): Promise<BookStackPageDetail> {
  const config = await toolConfigService.getBookstackConfig();
  if (!config.url || !isValidServiceUrl(config.url)) {
    throw new Error('BOOKSTACK_URL is not configured or is not a valid service URL');
  }

  try {
    const [response, bookSlugMap] = await Promise.all([
      httpClient.get(
        `${config.url}/api/pages/${id}`,
        { headers: buildHeaders(config) },
      ),
      getBookSlugMap(),
    ]);
    const page = response.data;
    const bookSlug = bookSlugMap.get(page.book_id) || page.book?.slug || page.book_slug || page.book_id;
    return {
      id: page.id,
      name: page.name,
      slug: page.slug,
      book_id: page.book_id,
      book_slug: page.book_slug ?? '',
      chapter_id: page.chapter_id ?? 0,
      html: page.html ?? '',
      created_at: page.created_at,
      updated_at: page.updated_at,
      url: `${config.url}/books/${bookSlug}/page/${page.slug}`,
      book: page.book,
      createdBy: page.created_by,
      updatedBy: page.updated_by,
    };
  } catch (error) {
    if (axios.isAxiosError(error)) {
      logger.error('BookStack getPageById failed', {
        status: error.response?.status,
        statusText: error.response?.statusText,
      });
    }
    logger.error('BookStackService.getPageById failed', { error });
    throw new Error('Failed to fetch BookStack page');
  }
}

export default { getRecentPages, searchPages, getPageById };
