"""
Core export logic with asyncio parallel processing, checkpointing, and resume.
"""

import asyncio
import csv
import json
from datetime import datetime, timezone
from pathlib import Path

from notion_bulk_export.block_parser import blocks_to_text
from notion_bulk_export.notion_api import NotionAPI
from notion_bulk_export.property_parser import get_page_title, parse_all_properties

CHECKPOINT_INTERVAL = 50


class NotionExporter:
    """Exports a Notion database to JSON/CSV with parallel processing."""

    def __init__(
        self,
        token: str,
        database_id: str,
        output_dir: str = "./output",
        concurrency: int = 5,
        include_content: bool = True,
        formats: list[str] | None = None,
    ):
        self._token = token
        self._database_id = database_id
        self._output_dir = Path(output_dir)
        self._concurrency = concurrency
        self._include_content = include_content
        self._formats = formats or ["json"]
        self._delay_between_batches = 0.5

    async def run(self) -> None:
        """Execute the full export pipeline."""
        start_time = datetime.now(timezone.utc)
        self._output_dir.mkdir(parents=True, exist_ok=True)

        print(f"[START] {start_time.strftime('%Y-%m-%d %H:%M:%S')} UTC")
        print(f"[INFO] Concurrency: {self._concurrency}")
        print(f"[INFO] Content: {'yes' if self._include_content else 'no'}")
        print()

        async with NotionAPI(self._token) as api:
            # 1. Fetch page list (or load from cache)
            pages = await self._load_or_fetch_pages(api)
            if not pages:
                print("[ERROR] No pages found in database.")
                return

            # 2. Load checkpoint
            processed, processed_ids = self._load_checkpoint()
            remaining = [p for p in pages if p["id"] not in processed_ids]

            print(f"[INFO] Total: {len(pages)} pages")
            if processed:
                print(f"[INFO] Resuming from {len(processed)} already processed")
            print(f"[INFO] Remaining: {len(remaining)} pages")
            print()

            # 3. Process in batches
            all_data = processed.copy()
            total = len(pages)

            for i in range(0, len(remaining), self._concurrency):
                batch = remaining[i : i + self._concurrency]
                batch_start = len(all_data) + 1

                print(
                    f"  [{batch_start}-{batch_start + len(batch) - 1}/{total}]"
                    " Processing..."
                )

                results = await self._process_batch(api, batch)
                all_data.extend(results)

                # Checkpoint
                if len(all_data) % CHECKPOINT_INTERVAL < self._concurrency:
                    self._save_checkpoint(all_data)
                    print(f"  [SAVED] {len(all_data)} pages")

                await asyncio.sleep(self._delay_between_batches)

            # 4. Write final output
            self._write_output(all_data)

            # 5. Cleanup checkpoint
            checkpoint_file = self._output_dir / "_checkpoint.json"
            if checkpoint_file.exists():
                checkpoint_file.unlink()

            end_time = datetime.now(timezone.utc)
            duration = end_time - start_time
            print()
            print("=" * 50)
            print(f"[DONE] Exported {len(all_data)} pages")
            print(f"[TIME] {duration}")
            print(f"[DIR]  {self._output_dir}")

    async def _load_or_fetch_pages(self, api: NotionAPI) -> list[dict]:
        """Fetch page list from API, or load from local index cache."""
        index_file = self._output_dir / "_pages_index.json"

        if index_file.exists():
            print("[INFO] Loading cached page index...")
            with open(index_file, "r", encoding="utf-8") as f:
                raw_pages = json.load(f)
            print(f"[OK] Loaded {len(raw_pages)} pages from cache")
            return raw_pages

        print("[INFO] Fetching page list from Notion API...")
        raw_pages = await api.query_database(self._database_id)

        # Normalize page data
        pages = []
        for page in raw_pages:
            properties = page.get("properties", {})
            pages.append(
                {
                    "id": page["id"],
                    "title": get_page_title(properties),
                    "properties": parse_all_properties(properties),
                    "created_time": page.get("created_time"),
                    "last_edited_time": page.get("last_edited_time"),
                    "url": page.get("url", ""),
                }
            )

        # Save index
        with open(index_file, "w", encoding="utf-8") as f:
            json.dump(pages, f, ensure_ascii=False, indent=2, default=str)

        print(f"[OK] Total {len(pages)} pages found")
        return pages

    async def _process_batch(self, api: NotionAPI, pages: list[dict]) -> list[dict]:
        """Process a batch of pages concurrently."""
        tasks = [self._process_page(api, page) for page in pages]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        valid = []
        for result in results:
            if isinstance(result, Exception):
                print(f"  [ERROR] {result}")
            else:
                valid.append(result)
        return valid

    async def _process_page(self, api: NotionAPI, page: dict) -> dict:
        """Process a single page: optionally fetch its block content."""
        if not self._include_content:
            return {**page, "content": "", "block_count": 0}

        blocks = await api.get_block_children(page["id"])
        content = blocks_to_text(blocks)

        return {
            **page,
            "content": content,
            "block_count": len(content.split("\n")) if content else 0,
        }

    def _load_checkpoint(self) -> tuple[list[dict], set[str]]:
        """Load previously processed pages from checkpoint."""
        checkpoint_file = self._output_dir / "_checkpoint.json"

        if not checkpoint_file.exists():
            return [], set()

        with open(checkpoint_file, "r", encoding="utf-8") as f:
            processed = json.load(f)

        processed_ids = {p["id"] for p in processed}
        return processed, processed_ids

    def _save_checkpoint(self, data: list[dict]) -> None:
        """Save current progress to checkpoint file."""
        checkpoint_file = self._output_dir / "_checkpoint.json"
        with open(checkpoint_file, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2, default=str)

    def _write_output(self, data: list[dict]) -> None:
        """Write the final output files."""
        if "json" in self._formats:
            self._write_json(data)

        if "csv" in self._formats:
            self._write_csv(data)

    def _write_json(self, data: list[dict]) -> None:
        """Write JSON output."""
        output_file = self._output_dir / "export.json"
        with open(output_file, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2, default=str)
        print(f"[FILE] {output_file}")

    def _write_csv(self, data: list[dict]) -> None:
        """Write CSV output with auto-detected columns."""
        if not data:
            return

        output_file = self._output_dir / "export.csv"

        # Collect all property keys across all pages
        all_prop_keys: dict[str, None] = {}
        for item in data:
            for key in item.get("properties", {}):
                all_prop_keys[key] = None

        # Build columns: fixed + dynamic properties + content
        fixed_cols = ["id", "title", "created_time", "last_edited_time", "url"]
        prop_cols = list(all_prop_keys.keys())
        content_cols = ["content", "block_count"] if self._include_content else []
        all_cols = fixed_cols + prop_cols + content_cols

        with open(output_file, "w", encoding="utf-8-sig", newline="") as f:
            writer = csv.writer(f)
            writer.writerow(all_cols)

            for item in data:
                row = []
                # Fixed columns
                for col in fixed_cols:
                    row.append(item.get(col, ""))

                # Property columns
                props = item.get("properties", {})
                for col in prop_cols:
                    val = props.get(col, "")
                    if isinstance(val, list):
                        val = ", ".join(str(v) for v in val)
                    row.append(val if val is not None else "")

                # Content columns
                if self._include_content:
                    content = item.get("content", "")
                    row.append(content.replace("\n", "\\n") if content else "")
                    row.append(item.get("block_count", 0))

                writer.writerow(row)

        print(f"[FILE] {output_file}")
