'use client';

/* eslint @suno-custom/no-tailwind-color-in-classnames: warn */

/* eslint jsx-a11y/click-events-have-key-events: warn */

/* eslint jsx-a11y/no-static-element-interactions: warn */
import { observer } from 'mobx-react-lite';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';

import Button, { ButtonVariant } from '@/components/button/Button';
import SpinnerSVG from '@/components/svg/SpinnerSVG';
import Textarea from '@/components/textarea/Textarea';
import TitleText from '@/components/title/TitleText';
import { useApiClient } from '@/lib/apiClient';

interface LyricsSimilaritySpec {
  lyrics_id: string;
  similarity_score: number;
  matched_tokens?: string[];
}

interface SimilarityCheckSpec {
  model_name: string;
  similarities: LyricsSimilaritySpec[];
}

interface CheckLyricsCopyrightInfringementResponse {
  is_copyright_infringement: boolean;
  details: SimilarityCheckSpec | null;
  metadata?: any;
}

interface CheckLyricsRequest {
  lyrics: string;
  threshold?: number;
  version: string;
}

const LyricsInfringementChecker = observer(() => {
  const router = useRouter();
  let env = 'staging';
  if (
    process.env.NEXT_PUBLIC_API_BASE === 'https://studio-api.suno.ai' ||
    process.env.NEXT_PUBLIC_API_BASE === 'https://studio-api.prod.suno.com'
  ) {
    env = 'prod';
  }
  if (env === 'prod') {
    router.push('/');
    return null;
  }

  const [lyrics, setLyrics] = useState('');
  const [threshold, setThreshold] = useState('0.9');
  const [result, setResult] =
    useState<CheckLyricsCopyrightInfringementResponse | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [requestTime, setRequestTime] = useState<number | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [version, setVersion] = useState('elasticsearch');
  const apiClient = useApiClient();
  const [expandedLyrics, setExpandedLyrics] = useState<Set<string>>(new Set());
  const [lyricsContent, setLyricsContent] = useState<Map<string, string>>(
    new Map()
  );
  const [cleanedLyricsMap, setCleanedLyricsMap] = useState<Map<string, string>>(
    new Map()
  );

  const handleRetrieveLyrics = useCallback(
    async (lyricsId: string) => {
      if (!lyricsId) return;
      try {
        const { data } = await apiClient.GET(
          '/api/generate/existing-lyrics-by-id/{lyrics_id}',
          {
            params: {
              path: { lyrics_id: lyricsId },
              query: { version },
            } as any,
          }
        );
        if (data && 'lyrics' in data) {
          setLyricsContent((prev) =>
            new Map(prev).set(lyricsId, data.lyrics || 'NOT_FOUND')
          );
        } else {
          throw new Error('Lyrics not found in response');
        }
      } catch (err) {
        setError((err as Error).message);
      }
    },
    [apiClient, version]
  );

  useEffect(() => {
    if (
      result?.details?.similarities &&
      result.details.similarities.length > 0
    ) {
      // Fetch lyrics for all similarities
      result.details.similarities.forEach((sim) => {
        if (!lyricsContent.has(sim.lyrics_id)) {
          handleRetrieveLyrics(sim.lyrics_id);
        }
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [result?.details?.similarities, handleRetrieveLyrics]);

  const handleCheck = async () => {
    setIsLoading(true);
    setError(null);
    const startTime = Date.now();

    try {
      const { data, response } = await apiClient.POST(
        '/api/generate/check-lyrics-copyright-infringement/',
        {
          body: {
            lyrics,
            threshold: parseFloat(threshold),
            version,
          } as CheckLyricsRequest,
        }
      );

      setRequestTime(Date.now() - startTime);

      if (!response.ok || !data) {
        throw new Error('Failed to check lyrics');
      }

      const typedData = data as CheckLyricsCopyrightInfringementResponse;
      setResult(typedData);
    } catch (err) {
      setRequestTime(Date.now() - startTime);
      setError((err as Error).message);
    } finally {
      setIsLoading(false);
    }
  };

  const handleAddCopyrightLyrics = async () => {
    setIsLoading(true);
    setError(null);
    try {
      const { response } = await apiClient.POST(
        '/api/moderation/add-copyright-lyrics/',
        {
          body: { lyrics },
        }
      );
      if (!response.ok) {
        throw new Error('Failed to add copyright lyrics');
      }
      await handleCheck();
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setIsLoading(false);
    }
  };

  const handleRemoveSpecificLyrics = async (lyricsId: string) => {
    setIsLoading(true);
    setError(null);
    try {
      const { response } = await apiClient.DELETE(
        '/api/moderation/delete-copyright-lyrics/{doc_id}/',
        {
          params: {
            path: {
              doc_id: lyricsId,
            },
          },
        }
      );
      if (!response.ok) {
        throw new Error('Failed to remove copyright lyrics');
      }
      await handleCheck();
    } catch (err) {
      const errorMessage = (err as Error).message;
      if (errorMessage.includes('Unexpected end of JSON input')) {
        // for some reason this only happens in staging... but the call still appears to succeed
        // so we'll just retry the check
        await handleCheck();
      } else {
        setError(errorMessage);
      }
    } finally {
      setIsLoading(false);
    }
  };

  const toggleLyrics = (lyricsId: string) => {
    setExpandedLyrics((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(lyricsId)) {
        newSet.delete(lyricsId);
      } else {
        newSet.add(lyricsId);
      }
      return newSet;
    });
    handleRetrieveLyrics(lyricsId);
  };

  const highlightMatchedTokens = (lyrics: string, matchedTokens: string[]) => {
    console.log('highlightMatchedTokens called with:', {
      lyrics: lyrics?.substring(0, 100),
      matchedTokens,
    });
    if (!lyrics || !matchedTokens || matchedTokens.length === 0) {
      return lyrics;
    }

    // Extract the actual song text from the token strings (part after closing parenthesis)
    const phrases = matchedTokens
      .map((token) => {
        const match = token.match(/\)\s*(.+)$/);
        const phrase = match ? match[1].trim() : token;
        // Remove quotes and clean up the phrase
        return phrase.replace(/^"|"$/g, '').trim();
      })
      .filter((phrase) => phrase.length > 0);

    if (phrases.length === 0) {
      return lyrics;
    }

    // Step 1: Find all matching positions
    const matches: Array<{ start: number; end: number; phrase: string }> = [];

    phrases.forEach((phrase) => {
      // Create a flexible regex that matches words with optional punctuation between them
      const words = phrase.split(/\s+/);
      const flexiblePattern = words
        .map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[^\\w]*')
        .join('\\s*');

      const regex = new RegExp(flexiblePattern, 'gi');
      let match;

      while ((match = regex.exec(lyrics)) !== null) {
        matches.push({
          start: match.index,
          end: match.index + match[0].length,
          phrase: phrase,
        });
      }
    });

    // Step 2: Merge overlapping matches
    const mergedMatches: Array<{
      start: number;
      end: number;
      phrases: string[];
    }> = [];

    // Sort matches by start position
    matches.sort((a, b) => a.start - b.start);

    for (const match of matches) {
      let merged = false;

      for (const existing of mergedMatches) {
        // Check if this match overlaps with existing match
        if (match.start <= existing.end && match.end >= existing.start) {
          // Merge the matches
          existing.start = Math.min(existing.start, match.start);
          existing.end = Math.max(existing.end, match.end);
          existing.phrases.push(match.phrase);
          merged = true;
          break;
        }
      }

      if (!merged) {
        mergedMatches.push({
          start: match.start,
          end: match.end,
          phrases: [match.phrase],
        });
      }
    }

    // Step 3: Apply highlights in reverse order to maintain positions
    let result = lyrics;
    mergedMatches.sort((a, b) => b.start - a.start); // Reverse order

    for (const match of mergedMatches) {
      const before = result.substring(0, match.start);
      const matched = result.substring(match.start, match.end);
      const after = result.substring(match.end);

      result =
        before +
        `<span class="bg-yellow-400 text-black px-1 rounded">${matched}</span>` +
        after;
    }

    // Convert HTML string to JSX with expandable matched phrases
    const phrasesText = phrases.join(', ');
    const isTruncated = phrasesText.length > 500;
    const displayText = isTruncated
      ? phrasesText.substring(0, 500) + '...'
      : phrasesText;

    return (
      <div>
        <div dangerouslySetInnerHTML={{ __html: result }} />
        <details className='mt-2 text-sm text-yellow-400'>
          <summary className='cursor-pointer hover:text-yellow-300'>
            Matched phrases: {displayText}
          </summary>
          {isTruncated && (
            <div className='mt-1 text-yellow-400'>{phrasesText}</div>
          )}
        </details>
      </div>
    );
  };

  const handleRemoveHighlightedPortions = (lyricsId: string) => {
    const lyrics = lyricsContent.get(lyricsId) || '';
    const matchedTokens =
      result?.details?.similarities?.find((s) => s.lyrics_id === lyricsId)
        ?.matched_tokens || [];

    if (!lyrics || !matchedTokens || matchedTokens.length === 0) {
      alert('No highlighted portions to remove');
      return;
    }

    // Extract the actual song text from the token strings (part after closing parenthesis)
    const phrases = matchedTokens
      .map((token) => {
        const match = token.match(/\)\s*(.+)$/);
        const phrase = match ? match[1].trim() : token;
        // Remove quotes and clean up the phrase
        return phrase.replace(/^"|"$/g, '').trim();
      })
      .filter((phrase) => phrase.length > 0);

    if (phrases.length === 0) {
      alert('No valid phrases found to remove');
      return;
    }

    // Step 1: Find all matching positions
    const matches: Array<{ start: number; end: number; phrase: string }> = [];

    phrases.forEach((phrase) => {
      // Create a flexible regex that matches words with optional punctuation between them
      const words = phrase.split(/\s+/);
      const flexiblePattern = words
        .map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[^\\w]*')
        .join('\\s*');

      const regex = new RegExp(flexiblePattern, 'gi');
      let match;

      while ((match = regex.exec(lyrics)) !== null) {
        matches.push({
          start: match.index,
          end: match.index + match[0].length,
          phrase: phrase,
        });
      }
    });

    // Step 2: Merge overlapping matches
    const mergedMatches: Array<{
      start: number;
      end: number;
      phrases: string[];
    }> = [];

    // Sort matches by start position
    matches.sort((a, b) => a.start - b.start);

    for (const match of matches) {
      let merged = false;

      for (const existing of mergedMatches) {
        // Check if this match overlaps with existing match
        if (match.start <= existing.end && match.end >= existing.start) {
          // Merge the matches
          existing.start = Math.min(existing.start, match.start);
          existing.end = Math.max(existing.end, match.end);
          existing.phrases.push(match.phrase);
          merged = true;
          break;
        }
      }

      if (!merged) {
        mergedMatches.push({
          start: match.start,
          end: match.end,
          phrases: [match.phrase],
        });
      }
    }

    // Step 3: Remove highlighted portions in reverse order to maintain positions
    let cleanedLyrics = lyrics;
    mergedMatches.sort((a, b) => b.start - a.start); // Reverse order

    for (const match of mergedMatches) {
      const before = cleanedLyrics.substring(0, match.start);
      const after = cleanedLyrics.substring(match.end);
      cleanedLyrics = before + after;
    }

    // Clean up extra whitespace and newlines
    cleanedLyrics = cleanedLyrics.replace(/\n\s*\n\s*\n/g, '\n\n').trim();

    // Store the cleaned lyrics
    setCleanedLyricsMap((prev) => new Map(prev).set(lyricsId, cleanedLyrics));
  };

  const handleSaveCleanedLyrics = async (lyricsId: string) => {
    const cleanedLyrics = cleanedLyricsMap.get(lyricsId);
    if (!cleanedLyrics) return;

    setIsLoading(true);
    setError(null);
    try {
      const { response } = await apiClient.POST(
        '/api/moderation/update-copyright-lyrics/{doc_id}/',
        {
          params: {
            path: {
              doc_id: lyricsId,
            },
          },
          body: {
            lyrics: cleanedLyrics,
          },
        }
      );
      if (!response.ok) {
        throw new Error('Failed to update copyright lyrics');
      }

      // Update the local cache
      setLyricsContent((prev) => new Map(prev).set(lyricsId, cleanedLyrics));

      // Clear the cleaned lyrics maps
      setCleanedLyricsMap((prev) => {
        const newMap = new Map(prev);
        newMap.delete(lyricsId);
        return newMap;
      });

      // Re-run the check to see updated results
      await handleCheck();
    } catch (err) {
      setError((err as Error).message);
    } finally {
      setIsLoading(false);
    }
  };

  const handleUndoCleanedLyrics = (lyricsId: string) => {
    setCleanedLyricsMap((prev) => {
      const newMap = new Map(prev);
      newMap.delete(lyricsId);
      return newMap;
    });
  };

  return (
    <main className='flex max-w-full flex-1 flex-col'>
      <div className='scrollbar-none mt-[-10px] flex max-w-full flex-1 flex-col overflow-y-auto md:mt-4'>
        <div className='flex max-w-full flex-1 flex-col'>
          <TitleText text='Lyrics Infringement Checker' />
          <div className='mt-2 flex flex-col'>
            <Textarea
              value={lyrics}
              onChange={(e) => setLyrics(e.target.value)}
              placeholder='Enter lyrics here...'
              customHeight='h-[300px]'
              maxLength={5000}
              isScrollable={true}
            />
            <div className='mb-2 flex items-center'>
              <span className='mr-2 font-sans'>Threshold</span>
              <input
                type='number'
                value={threshold}
                onChange={(e) => setThreshold(e.target.value)}
                placeholder='Enter threshold (0-1)'
                className='placeholder-opacity-50 mr-4 w-24 rounded border border-quaternary bg-transparent px-2 py-1 font-sans text-primary placeholder-primary'
                step='0.01'
                min='0'
                max='1'
              />
              <span className='mr-2 font-sans'>Version</span>
              <select
                value={version}
                onChange={(e) => setVersion(e.target.value)}
                className='mr-4 rounded border border-quaternary bg-transparent px-2 py-1 font-sans text-primary'
              >
                <option value='elasticsearch'>Elasticsearch</option>
              </select>
              <Button
                variant={ButtonVariant.Primary}
                onClick={handleCheck}
                disabled={isLoading}
              >
                Check for Infringement
              </Button>
              <Button
                variant={ButtonVariant.Secondary}
                onClick={handleAddCopyrightLyrics}
                disabled={isLoading || !lyrics}
                className='ml-2'
              >
                Add Copyright Lyrics
              </Button>
              {isLoading ? (
                <SpinnerSVG className='ml-3' />
              ) : (
                requestTime && (
                  <span className='ml-3 font-sans text-sm text-primary'>
                    {requestTime}ms
                  </span>
                )
              )}
            </div>
            {error && (
              <p className='mt-2 font-sans text-accent-error-on-primary'>
                {error}
              </p>
            )}
            {result && (
              <div className='mt-2'>
                <div className='rounded-lg border border-quaternary bg-tertiary p-4'>
                  <div className='mb-4 flex items-center justify-between'>
                    <span className='font-sans font-bold'>
                      Copyright Infringement:{' '}
                      <span
                        className={`rounded px-3 py-1 text-sm ${
                          result.is_copyright_infringement
                            ? 'bg-opacity-50 bg-green-900 text-green-400'
                            : 'bg-opacity-50 bg-red-900 text-red-400'
                        }`}
                      >
                        {result.is_copyright_infringement ? 'Yes' : 'No'}
                      </span>
                    </span>
                    <span className='bg-opacity-50 rounded bg-green-900 px-3 py-1 font-sans text-sm text-green-400'>
                      Matching Lyrics:{' '}
                      {result.details?.similarities?.length === 20
                        ? '20+'
                        : result.details?.similarities?.length || 0}
                    </span>
                  </div>

                  {result.metadata && (
                    <div className='mt-2'>
                      <p className='font-sans'>Metadata:</p>
                      <pre className='overflow-x-auto font-mono text-sm'>
                        {JSON.stringify(result.metadata, null, 2)}
                      </pre>
                    </div>
                  )}

                  {result.details && (
                    <div className='mt-2 space-y-2'>
                      <ul className='space-y-2'>
                        {result.details.similarities.map((similarity) => (
                          <li key={similarity.lyrics_id} className='font-sans'>
                            <div
                              className='hover:bg-opacity-20 flex cursor-pointer items-center justify-between rounded border border-quaternary p-2 hover:bg-quaternary'
                              onClick={() => toggleLyrics(similarity.lyrics_id)}
                            >
                              <div className='flex-1'>
                                <div className='font-bold'>
                                  ID: {similarity.lyrics_id.substring(0, 6)}...
                                </div>
                                <div className='mt-1 text-sm text-gray-400'>
                                  {lyricsContent.get(similarity.lyrics_id)
                                    ? lyricsContent
                                        .get(similarity.lyrics_id)!
                                        .split(' ')
                                        .slice(0, 20)
                                        .join(' ') + '...'
                                    : 'Loading...'}
                                </div>
                              </div>
                              <div className='ml-2 text-sm text-primary transition-transform duration-200'>
                                {expandedLyrics.has(similarity.lyrics_id)
                                  ? '▲'
                                  : '▼'}
                              </div>
                            </div>
                            {expandedLyrics.has(similarity.lyrics_id) && (
                              <div
                                className='mt-2 rounded border border-quaternary p-2'
                                style={{ backgroundColor: '#252020' }}
                              >
                                <div className='mb-4 flex justify-end space-x-2'>
                                  {cleanedLyricsMap.has(
                                    similarity.lyrics_id
                                  ) ? (
                                    <>
                                      <Button
                                        variant={ButtonVariant.Primary}
                                        onClick={() =>
                                          handleSaveCleanedLyrics(
                                            similarity.lyrics_id
                                          )
                                        }
                                        disabled={isLoading}
                                        className='bg-green-600 px-3 py-1 text-sm hover:bg-green-700'
                                      >
                                        Save Changes
                                      </Button>
                                      <Button
                                        variant={ButtonVariant.Secondary}
                                        onClick={() =>
                                          handleUndoCleanedLyrics(
                                            similarity.lyrics_id
                                          )
                                        }
                                        disabled={isLoading}
                                        className='px-3 py-1 text-sm'
                                      >
                                        Undo
                                      </Button>
                                    </>
                                  ) : (
                                    <>
                                      <Button
                                        variant={ButtonVariant.Secondary}
                                        onClick={() =>
                                          handleRemoveHighlightedPortions(
                                            similarity.lyrics_id
                                          )
                                        }
                                        disabled={isLoading}
                                        className='bg-yellow-400 px-3 py-1 text-sm text-black hover:bg-yellow-500'
                                      >
                                        Remove Highlighted Portions
                                      </Button>
                                      <Button
                                        variant={ButtonVariant.Secondary}
                                        onClick={(e) => {
                                          e.stopPropagation();
                                          handleRemoveSpecificLyrics(
                                            similarity.lyrics_id
                                          );
                                        }}
                                        disabled={isLoading}
                                        className='bg-red-600 px-3 py-1 text-sm hover:bg-red-700'
                                      >
                                        Remove Document
                                      </Button>
                                    </>
                                  )}
                                </div>
                                <div className='font-sans whitespace-pre-wrap text-white'>
                                  {cleanedLyricsMap.has(
                                    similarity.lyrics_id
                                  ) ? (
                                    <div>
                                      <div className='font-sans whitespace-pre-wrap text-white'>
                                        {cleanedLyricsMap.get(
                                          similarity.lyrics_id
                                        )}
                                      </div>
                                      <div className='mt-2 text-sm text-yellow-400'>
                                        Highlighted portions have been removed
                                      </div>
                                    </div>
                                  ) : (
                                    highlightMatchedTokens(
                                      lyricsContent.get(similarity.lyrics_id) ||
                                        '',
                                      similarity.matched_tokens || []
                                    )
                                  )}
                                </div>
                              </div>
                            )}
                          </li>
                        ))}
                      </ul>
                    </div>
                  )}
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </main>
  );
});

export default LyricsInfringementChecker;
