import { DocsCommentStatus } from '@prisma/client';
import { prisma } from '../../config/database';
import { redis } from '../../config/redis';
import { logger } from '../../utils/logger';
import { giteaClient } from '../../services/gitea.client';

const ISSUE_CACHE_PREFIX = 'docs-comment-issue:';
const ISSUE_CACHE_TTL = 300; // 5 minutes

// HTML comment prefix used to identify anonymous comments in Gitea
const ANON_PREFIX_RE = /^<!-- docs-comment: anonymous \| name: (.+?) \| id: (\S+) -->\n/;

function buildAnonPrefix(authorName: string, id: string): string {
  return `<!-- docs-comment: anonymous | name: ${authorName} | id: ${id} -->\n`;
}

function pagePathToIssueTitle(pagePath: string): string {
  // Normalize: strip leading/trailing slashes, default to "index"
  const normalized = pagePath.replace(/^\/+|\/+$/g, '') || 'index';
  return `Comments: ${normalized}`;
}

export const docsCommentsService = {
  /**
   * Get or create the Gitea issue for a given page path.
   * Uses Redis cache to avoid repeated Gitea API calls.
   */
  async getOrCreateIssue(pagePath: string): Promise<number | null> {
    const title = pagePathToIssueTitle(pagePath);
    const cacheKey = `${ISSUE_CACHE_PREFIX}${title}`;

    // Check Redis cache
    const cached = await redis.get(cacheKey).catch(() => null);
    if (cached) return parseInt(cached, 10);

    // Search Gitea
    const existing = await giteaClient.findIssueByTitle(title);
    if (existing) {
      await redis.setex(cacheKey, ISSUE_CACHE_TTL, existing.number.toString()).catch(() => {});
      return existing.number;
    }

    // Create new issue
    const issue = await giteaClient.createIssue(
      title,
      `Comments for documentation page: \`${pagePath}\`\n\n_This issue is managed automatically by Changemaker Lite._`,
    );
    if (!issue) return null;

    await redis.setex(cacheKey, ISSUE_CACHE_TTL, issue.number.toString()).catch(() => {});
    return issue.number;
  },

  /**
   * Get approved comments for a page.
   * Returns both authenticated Gitea comments and approved anonymous comments.
   */
  async getPageComments(pagePath: string, page: number = 1) {
    const title = pagePathToIssueTitle(pagePath);

    // Find the issue (don't create if it doesn't exist — no comments yet)
    const existing = await giteaClient.findIssueByTitle(title);
    if (!existing) {
      return { comments: [], total: 0, page };
    }

    // Fetch all comments from Gitea
    const giteaComments = await giteaClient.listIssueComments(existing.number);

    // Get approved anonymous comment IDs from DB
    const approvedAnon = await prisma.docsComment.findMany({
      where: {
        pagePath,
        giteaIssueNumber: existing.number,
        status: DocsCommentStatus.APPROVED,
      },
      select: { giteaCommentId: true, authorName: true },
    });
    const approvedAnonIds = new Set(approvedAnon.map((a) => a.giteaCommentId));
    const anonNameMap = new Map(approvedAnon.map((a) => [a.giteaCommentId, a.authorName]));

    // Filter and transform comments
    const comments = giteaComments
      .map((gc) => {
        const anonMatch = gc.body.match(ANON_PREFIX_RE);

        if (anonMatch) {
          // Anonymous comment — only include if approved
          if (!approvedAnonIds.has(BigInt(gc.id))) return null;

          return {
            id: gc.id,
            body: gc.body.replace(ANON_PREFIX_RE, ''),
            authorName: anonNameMap.get(BigInt(gc.id)) || anonMatch[1],
            avatarUrl: null,
            isAnonymous: true,
            createdAt: gc.created_at,
          };
        }

        // Authenticated comment — always show
        return {
          id: gc.id,
          body: gc.body,
          authorName: gc.user.full_name || gc.user.login,
          avatarUrl: gc.user.avatar_url,
          isAnonymous: false,
          createdAt: gc.created_at,
        };
      })
      .filter(Boolean);

    // Simple pagination (Gitea already limits to 50)
    const pageSize = 20;
    const start = (page - 1) * pageSize;
    const paginated = comments.slice(start, start + pageSize);

    return { comments: paginated, total: comments.length, page };
  },

  /**
   * Post an anonymous comment. Creates a DocsComment row for moderation.
   */
  async postAnonymousComment(params: {
    pagePath: string;
    authorName: string;
    authorEmail?: string;
    body: string;
  }) {
    const issueNumber = await this.getOrCreateIssue(params.pagePath);
    if (!issueNumber) {
      throw new Error('Failed to create or find Gitea issue for this page');
    }

    // Create the DB row first to get the ID
    const dbComment = await prisma.docsComment.create({
      data: {
        pagePath: params.pagePath,
        giteaIssueNumber: issueNumber,
        giteaCommentId: BigInt(0), // Will be updated after Gitea post
        authorName: params.authorName,
        authorEmail: params.authorEmail,
        status: DocsCommentStatus.PENDING,
      },
    });

    // Post to Gitea with metadata prefix
    const prefix = buildAnonPrefix(params.authorName, dbComment.id);
    const giteaComment = await giteaClient.createIssueComment(
      issueNumber,
      `${prefix}${params.body}`,
    );

    if (!giteaComment) {
      // Clean up DB row if Gitea post failed
      await prisma.docsComment.delete({ where: { id: dbComment.id } }).catch(() => {});
      throw new Error('Failed to post comment to Gitea');
    }

    // Update DB row with actual Gitea comment ID
    await prisma.docsComment.update({
      where: { id: dbComment.id },
      data: { giteaCommentId: BigInt(giteaComment.id) },
    });

    logger.info(`Docs comment: anonymous comment by "${params.authorName}" on ${params.pagePath} (pending moderation)`);

    return { id: dbComment.id, status: 'pending' };
  },

  /**
   * Post an authenticated comment via user's Gitea OAuth token.
   * No moderation needed — appears immediately.
   */
  async postAuthenticatedComment(params: {
    pagePath: string;
    body: string;
    userToken: string;
  }) {
    const issueNumber = await this.getOrCreateIssue(params.pagePath);
    if (!issueNumber) {
      throw new Error('Failed to create or find Gitea issue for this page');
    }

    const giteaComment = await giteaClient.createIssueComment(
      issueNumber,
      params.body,
      params.userToken,
    );

    if (!giteaComment) {
      throw new Error('Failed to post comment to Gitea');
    }

    logger.info(`Docs comment: authenticated comment by "${giteaComment.user.login}" on ${params.pagePath}`);

    return {
      id: giteaComment.id,
      body: giteaComment.body,
      authorName: giteaComment.user.full_name || giteaComment.user.login,
      avatarUrl: giteaComment.user.avatar_url,
      isAnonymous: false,
      createdAt: giteaComment.created_at,
    };
  },

  /**
   * Moderation: approve or reject an anonymous comment
   */
  async moderateComment(commentId: string, action: 'approve' | 'reject', reviewedBy: string) {
    const comment = await prisma.docsComment.findUnique({ where: { id: commentId } });
    if (!comment) throw new Error('Comment not found');

    if (action === 'reject') {
      // Delete from Gitea + update DB
      if (comment.giteaCommentId > 0) {
        await giteaClient.deleteIssueComment(Number(comment.giteaCommentId));
      }

      await prisma.docsComment.update({
        where: { id: commentId },
        data: {
          status: DocsCommentStatus.REJECTED,
          reviewedAt: new Date(),
          reviewedBy,
        },
      });

      logger.info(`Docs comment: rejected comment ${commentId} by "${comment.authorName}"`);
      return { status: 'rejected' };
    }

    // Approve
    await prisma.docsComment.update({
      where: { id: commentId },
      data: {
        status: DocsCommentStatus.APPROVED,
        reviewedAt: new Date(),
        reviewedBy,
      },
    });

    logger.info(`Docs comment: approved comment ${commentId} by "${comment.authorName}"`);
    return { status: 'approved' };
  },

  /**
   * Get moderation queue
   */
  async getModerationQueue(params: {
    status?: DocsCommentStatus;
    page: number;
    pageSize: number;
  }) {
    const where = params.status ? { status: params.status } : {};
    const [items, total] = await Promise.all([
      prisma.docsComment.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip: (params.page - 1) * params.pageSize,
        take: params.pageSize,
      }),
      prisma.docsComment.count({ where }),
    ]);

    // Fetch comment body from Gitea for each item
    const enriched = await Promise.all(
      items.map(async (item) => {
        let body = '';
        if (item.giteaCommentId > 0) {
          try {
            const comments = await giteaClient.listIssueComments(item.giteaIssueNumber);
            const gc = comments.find((c) => c.id === Number(item.giteaCommentId));
            if (gc) {
              body = gc.body.replace(ANON_PREFIX_RE, '');
            }
          } catch {
            body = '[Unable to fetch comment body]';
          }
        }

        return {
          id: item.id,
          pagePath: item.pagePath,
          authorName: item.authorName,
          authorEmail: item.authorEmail,
          body,
          status: item.status,
          reviewedAt: item.reviewedAt,
          reviewedBy: item.reviewedBy,
          createdAt: item.createdAt,
        };
      }),
    );

    return { items: enriched, total, page: params.page, pageSize: params.pageSize };
  },

  /**
   * Get statistics for the admin dashboard
   */
  async getStats() {
    const [pending, approved, rejected, totalPages] = await Promise.all([
      prisma.docsComment.count({ where: { status: DocsCommentStatus.PENDING } }),
      prisma.docsComment.count({ where: { status: DocsCommentStatus.APPROVED } }),
      prisma.docsComment.count({ where: { status: DocsCommentStatus.REJECTED } }),
      prisma.docsComment.groupBy({
        by: ['pagePath'],
        _count: true,
      }),
    ]);

    return { pending, approved, rejected, totalPages: totalPages.length };
  },

  /**
   * Admin setup: create the repo and labels in Gitea
   */
  async setup() {
    const repo = await giteaClient.createRepo();
    if (!repo) throw new Error('Failed to create Gitea repository');

    const labels = await Promise.all([
      giteaClient.findOrCreateLabel('docs-page', '#0075ca'),
      giteaClient.findOrCreateLabel('anonymous', '#e4e669'),
      giteaClient.findOrCreateLabel('moderated', '#0e8a16'),
    ]);

    return { repo: repo.full_name ?? `${repo.name}`, labels: labels.filter(Boolean).map((l) => l!.name) };
  },
};
