"""CyberLeninka integration for Russian academic papers.

CyberLeninka is a Russian open access repository with scientific articles
from Russian journals. Supports filtering by VAK, RSCI, and SCOPUS indexed journals.
"""
from typing import List, Optional, Dict
from datetime import datetime
from curl_cffi import requests
from bs4 import BeautifulSoup
import re
import os
import logging
import time

from ..paper import Paper
from ..pdf_utils import extract_text_from_pdf

logger = logging.getLogger(__name__)


class CyberLeninkaSearcher:
    """Searcher for CyberLeninka Russian academic repository.

    Supports filtering by:
    - VAK (ВАК) - Russian Higher Attestation Commission journals
    - RSCI (РИНЦ) - Russian Science Citation Index
    - SCOPUS - Scopus indexed journals
    - Subject categories (e.g., economics, law, medicine)
    """

    BASE_URL = "https://cyberleninka.ru"
    API_URL = f"{BASE_URL}/api/search"

    # Catalog IDs for journal indexing filters
    CATALOGS = {
        "vak": 8,      # ВАК
        "rsci": 22,    # РИНЦ
        "scopus": 2,   # SCOPUS
    }

    # Common subject category IDs (terms)
    CATEGORIES = {
        "economics": 35,
        "law": 36,
        "medicine": 43,
        "psychology": 46,
        "sociology": 47,
        "pedagogy": 44,
        "philosophy": 48,
        "history": 41,
        "politics": 45,
        "philology": 49,
    }

    def __init__(self):
        """Initialize CyberLeninka searcher."""
        self.impersonate = "chrome"
        self.last_request_time = 0

    def _rate_limit(self, delay: float = 0.5):
        """Apply rate limiting."""
        elapsed = time.time() - self.last_request_time
        if elapsed < delay:
            time.sleep(delay - elapsed)
        self.last_request_time = time.time()

    def search(
        self,
        query: str,
        max_results: int = 10,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
        catalog: Optional[str] = None,
        category: Optional[str] = None,
        **kwargs
    ) -> List[Paper]:
        """Search CyberLeninka for papers.

        Args:
            query: Search query string
            max_results: Maximum number of papers (default: 10, max: 100)
            date_from: Start date YYYY-MM-DD (only year is used)
            date_to: End date YYYY-MM-DD (only year is used)
            catalog: Filter by indexing: 'vak', 'rsci', or 'scopus'
            category: Filter by subject: 'economics', 'law', 'medicine', etc.
            **kwargs: Additional parameters

        Returns:
            List of Paper objects
        """
        papers = []

        try:
            self._rate_limit()

            # Build request payload
            payload = {
                "q": query,
                "mode": "articles",
                "size": min(max_results, 100),
                "from": kwargs.get("offset", 0)
            }

            # Date filters (year only)
            if date_from:
                payload["year_from"] = int(date_from[:4])
            if date_to:
                payload["year_to"] = int(date_to[:4])

            # Catalog filter (VAK, RSCI, SCOPUS)
            if catalog and catalog.lower() in self.CATALOGS:
                payload["catalogs"] = [self.CATALOGS[catalog.lower()]]

            # Category/subject filter
            if category and category.lower() in self.CATEGORIES:
                payload["terms"] = [self.CATEGORIES[category.lower()]]

            response = requests.post(
                self.API_URL,
                json=payload,
                timeout=30,
                impersonate=self.impersonate
            )

            if response.status_code != 200:
                logger.error(f"CyberLeninka search failed with status {response.status_code}")
                return papers

            data = response.json()
            articles = data.get("articles", [])

            logger.info(f"CyberLeninka search: found {len(articles)} articles for '{query}'")

            for article in articles:
                try:
                    paper = self._parse_search_result(article)
                    if paper:
                        papers.append(paper)
                except Exception as e:
                    logger.warning(f"Error parsing CyberLeninka result: {e}")
                    continue

        except Exception as e:
            logger.error(f"CyberLeninka search error: {e}")

        return papers

    def _parse_search_result(self, article: Dict) -> Optional[Paper]:
        """Parse a search result into a Paper object."""
        try:
            # Extract article ID from link
            link = article.get("link", "")
            paper_id = link.split("/")[-1] if link else ""

            title = article.get("name", "")
            if not title:
                return None

            # Authors
            authors = []
            authors_data = article.get("authors", [])
            if isinstance(authors_data, list):
                for author in authors_data:
                    if isinstance(author, dict):
                        name = author.get("name", "")
                    else:
                        name = str(author)
                    if name:
                        authors.append(name)

            # Abstract/annotation
            abstract = article.get("annotation", "") or ""

            # Year
            year = article.get("year")
            published_date = datetime(int(year), 1, 1) if year else datetime.min

            # Journal info
            journal = article.get("journal", {})
            journal_name = journal.get("name", "") if isinstance(journal, dict) else ""

            # URL
            url = f"{self.BASE_URL}{link}" if link else ""
            pdf_url = f"{url}/pdf" if url else ""

            # Keywords
            keywords = []
            kw_data = article.get("keywords", [])
            if isinstance(kw_data, list):
                keywords = [k for k in kw_data if k]

            return Paper(
                paper_id=paper_id,
                title=title,
                authors=authors,
                abstract=abstract[:5000] if abstract else "",
                doi="",  # DOI requires fetching article page
                published_date=published_date,
                pdf_url=pdf_url,
                url=url,
                source="cyberleninka",
                journal=journal_name,
                categories=[],
                keywords=keywords[:10],
                citations=0,
                references=[],
                year_only=True,
                extra={
                    "year": year,
                    "cyberleninka_id": paper_id
                }
            )

        except Exception as e:
            logger.error(f"Error parsing CyberLeninka result: {e}")
            return None

    def get_paper_by_id(self, paper_id: str) -> Optional[Paper]:
        """Get a specific paper by its CyberLeninka ID (URL slug).

        Args:
            paper_id: CyberLeninka article slug (e.g., 'tsifrovoy-suverenitet-i-strany-vostoka')

        Returns:
            Paper object or None
        """
        try:
            self._rate_limit()

            url = f"{self.BASE_URL}/article/n/{paper_id}"
            response = requests.get(url, timeout=30, impersonate=self.impersonate)

            if response.status_code == 404:
                return None
            response.raise_for_status()

            return self._parse_article_page(response.text, paper_id, url)

        except Exception as e:
            logger.error(f"Error fetching CyberLeninka paper {paper_id}: {e}")
            return None

    def _parse_article_page(self, html: str, paper_id: str, url: str) -> Optional[Paper]:
        """Parse an article page into a Paper object."""
        try:
            soup = BeautifulSoup(html, 'lxml')

            # Title from meta tag
            title = ""
            title_meta = soup.find("meta", {"name": "citation_title"})
            if title_meta:
                title = title_meta.get("content", "")
            if not title:
                title_meta = soup.find("meta", {"property": "og:title"})
                if title_meta:
                    title = title_meta.get("content", "")

            if not title:
                return None

            # Authors from meta tags
            authors = []
            for meta in soup.find_all("meta", {"name": "citation_author"}):
                name = meta.get("content", "")
                if name:
                    authors.append(name)

            # Abstract from meta description
            abstract = ""
            desc_meta = soup.find("meta", {"name": "description"})
            if desc_meta:
                abstract = desc_meta.get("content", "")

            # DOI
            doi = ""
            doi_div = soup.find("div", {"class": "label-doi"})
            if doi_div:
                doi_match = re.search(r'DOI:\s*(10\.\S+)', doi_div.get("title", ""))
                if doi_match:
                    doi = doi_match.group(1)

            # Publication date
            published_date = datetime.min
            date_meta = soup.find("meta", {"name": "citation_publication_date"})
            if date_meta:
                date_str = date_meta.get("content", "")
                try:
                    published_date = datetime.strptime(date_str, "%Y")
                except:
                    pass

            # Journal
            journal = ""
            journal_meta = soup.find("meta", {"name": "citation_journal_title"})
            if journal_meta:
                journal = journal_meta.get("content", "")

            # Keywords
            keywords = []
            kw_meta = soup.find("meta", {"name": "citation_keywords"})
            if kw_meta:
                kw_text = kw_meta.get("content", "")
                keywords = [k.strip() for k in kw_text.split(",") if k.strip()]

            # PDF URL
            pdf_url = ""
            pdf_meta = soup.find("meta", {"name": "citation_pdf_url"})
            if pdf_meta:
                pdf_url = pdf_meta.get("content", "")

            # ISSN
            issn = ""
            issn_meta = soup.find("meta", {"name": "citation_issn"})
            if issn_meta:
                issn = issn_meta.get("content", "")

            # Subject categories from article section
            categories = []
            section_el = soup.find("i", {"itemprop": "articleSection"})
            if section_el:
                categories.append(section_el.get_text(strip=True))

            # Volume, issue, pages from meta tags
            volume = ""
            volume_meta = soup.find("meta", {"name": "citation_volume"})
            if volume_meta:
                volume = volume_meta.get("content", "")

            issue = ""
            issue_meta = soup.find("meta", {"name": "citation_issue"})
            if issue_meta:
                issue = issue_meta.get("content", "")

            first_page = ""
            fp_meta = soup.find("meta", {"name": "citation_firstpage"})
            if fp_meta:
                first_page = fp_meta.get("content", "")

            last_page = ""
            lp_meta = soup.find("meta", {"name": "citation_lastpage"})
            if lp_meta:
                last_page = lp_meta.get("content", "")

            pages = f"{first_page}-{last_page}" if first_page and last_page else first_page

            # License
            license_info = ""
            cc_div = soup.find("div", {"class": "label-cc"})
            if cc_div:
                license_info = cc_div.get_text(strip=True)

            # VAK status
            vak = bool(soup.find("div", {"class": "label"}, string=lambda t: t and "ВАК" in t))

            return Paper(
                paper_id=paper_id,
                title=title,
                authors=authors,
                abstract=abstract[:5000] if abstract else "",
                doi=doi,
                published_date=published_date,
                pdf_url=pdf_url or f"{url}/pdf",
                url=url,
                source="cyberleninka",
                journal=journal,
                categories=categories,
                keywords=keywords[:10],
                citations=0,
                references=[],
                year_only=True,
                extra={
                    "issn": issn,
                    "volume": volume,
                    "issue": issue,
                    "pages": pages,
                    "license": license_info,
                    "vak": vak,
                    "cyberleninka_id": paper_id
                }
            )

        except Exception as e:
            logger.error(f"Error parsing CyberLeninka page: {e}")
            return None

    def download_pdf(self, paper_id: str, save_path: str = "./downloads") -> str:
        """Download PDF from CyberLeninka.

        Args:
            paper_id: CyberLeninka article slug
            save_path: Directory to save

        Returns:
            Path to PDF or error message
        """
        try:
            os.makedirs(save_path, exist_ok=True)

            self._rate_limit()

            # PDF URL format
            pdf_url = f"{self.BASE_URL}/article/n/{paper_id}/pdf"

            response = requests.get(pdf_url, timeout=60, impersonate=self.impersonate)

            if response.status_code != 200:
                return f"PDF download failed for {paper_id}"

            content_type = response.headers.get("Content-Type", "")

            if "pdf" in content_type.lower():
                filename = f"cyberleninka_{paper_id}.pdf"
                file_path = os.path.join(save_path, filename)

                with open(file_path, 'wb') as f:
                    f.write(response.content)

                return file_path

            return f"PDF not available for {paper_id}"

        except Exception as e:
            logger.error(f"Error downloading CyberLeninka PDF: {e}")
            return f"Failed to download PDF: {e}"

    def read_paper(self, paper_id: str, save_path: str = "./downloads") -> str:
        """Read and extract text from a CyberLeninka paper.

        Tries PDF extraction first, falls back to scraping article page text.

        Args:
            paper_id: CyberLeninka article slug
            save_path: Directory for PDF storage

        Returns:
            Extracted text or error message
        """
        # Try PDF extraction first
        pdf_path = self.download_pdf(paper_id, save_path)
        if os.path.exists(pdf_path):
            text = extract_text_from_pdf(pdf_path)
            if text and text.strip():
                return text

        # Fallback: extract text from article HTML page
        return self._read_from_html(paper_id)

    def _read_from_html(self, paper_id: str) -> str:
        """Extract article text from the CyberLeninka HTML page."""
        try:
            self._rate_limit()

            url = f"{self.BASE_URL}/article/n/{paper_id}"
            response = requests.get(url, timeout=30, impersonate=self.impersonate)

            if response.status_code != 200:
                return f"Failed to read paper {paper_id}: HTTP {response.status_code}"

            soup = BeautifulSoup(response.text, 'lxml')

            # Build metadata header
            parts = []

            title_meta = soup.find("meta", {"name": "citation_title"})
            if title_meta and title_meta.get("content"):
                parts.append(f"Title: {title_meta['content']}")

            authors = [m.get("content", "") for m in soup.find_all("meta", {"name": "citation_author"}) if m.get("content")]
            if authors:
                parts.append(f"Authors: {', '.join(authors)}")

            journal_meta = soup.find("meta", {"name": "citation_journal_title"})
            if journal_meta and journal_meta.get("content"):
                parts.append(f"Journal: {journal_meta['content']}")

            date_meta = soup.find("meta", {"name": "citation_publication_date"})
            if date_meta and date_meta.get("content"):
                parts.append(f"Year: {date_meta['content']}")

            if parts:
                parts.append("")  # blank line separator

            # Extract article body text
            # CyberLeninka uses div with class "ocr" for full text
            body_div = soup.find("div", {"class": "ocr"})
            if not body_div:
                # Try alternative selectors
                body_div = soup.find("div", {"itemprop": "articleBody"})
            if not body_div:
                body_div = soup.find("div", {"class": "full-text"})

            if body_div:
                # Get text with paragraph separation
                paragraphs = body_div.find_all(["p", "h2", "h3", "h4"])
                if paragraphs:
                    for p in paragraphs:
                        text = p.get_text(strip=True)
                        if text:
                            parts.append(text)
                else:
                    body_text = body_div.get_text(separator="\n", strip=True)
                    if body_text:
                        parts.append(body_text)

            if len(parts) <= 4:  # Only metadata, no body text
                # Try description as last resort
                desc_meta = soup.find("meta", {"name": "description"})
                if desc_meta and desc_meta.get("content"):
                    parts.append(f"Abstract: {desc_meta['content']}")
                else:
                    return f"No text content found for paper {paper_id}"

            return "\n".join(parts)

        except Exception as e:
            logger.error(f"Error reading CyberLeninka article HTML: {e}")
            return f"Failed to read paper: {e}"
