"""In-memory index building for Kanbus issues."""

from __future__ import annotations

from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
import json
import os
from pathlib import Path
from typing import Dict, List

from kanbus.models import IssueData


def _load_issue_data(issue_path: Path) -> IssueData:
    """Load a single issue JSON file into an IssueData model.

    :param issue_path: Path to the issue JSON file.
    :type issue_path: Path
    :return: Parsed issue model.
    :rtype: IssueData
    """
    payload = json.loads(issue_path.read_bytes())
    return IssueData.model_validate(payload)


def _load_issue_batch(issue_paths: List[Path]) -> List[IssueData]:
    """Load a batch of issue JSON files into IssueData models.

    :param issue_paths: List of issue JSON paths.
    :type issue_paths: List[Path]
    :return: Parsed issue models.
    :rtype: List[IssueData]
    """
    return [_load_issue_data(issue_path) for issue_path in issue_paths]


def _add_issue_to_index(index: IssueIndex, issue: IssueData) -> None:
    """Add an IssueData instance to all index lookup tables.

    :param index: Index to update.
    :type index: IssueIndex
    :param issue: Issue data to register.
    :type issue: IssueData
    """
    index.by_id[issue.identifier] = issue
    index.by_status.setdefault(issue.status, []).append(issue)
    index.by_type.setdefault(issue.issue_type, []).append(issue)
    if issue.parent is not None:
        index.by_parent.setdefault(issue.parent, []).append(issue)
    for label in issue.labels:
        index.by_label.setdefault(label, []).append(issue)
    for dependency in issue.dependencies:
        if dependency.dependency_type == "blocked-by":
            index.reverse_dependencies.setdefault(dependency.target, []).append(issue)


@dataclass
class IssueIndex:
    """In-memory lookup tables for issues."""

    by_id: Dict[str, IssueData] = field(default_factory=dict)
    by_status: Dict[str, List[IssueData]] = field(default_factory=dict)
    by_type: Dict[str, List[IssueData]] = field(default_factory=dict)
    by_parent: Dict[str, List[IssueData]] = field(default_factory=dict)
    by_label: Dict[str, List[IssueData]] = field(default_factory=dict)
    reverse_dependencies: Dict[str, List[IssueData]] = field(default_factory=dict)


def build_index_from_directory(issues_directory: Path) -> IssueIndex:
    """Build an IssueIndex by scanning issue files in a directory.

    :param issues_directory: Directory containing issue JSON files.
    :type issues_directory: Path
    :return: In-memory issue index.
    :rtype: IssueIndex
    """
    index = IssueIndex()
    issue_entries = [
        (entry.name, entry.path)
        for entry in os.scandir(issues_directory)
        if entry.is_file() and entry.name.endswith(".json")
    ]
    issue_entries.sort(key=lambda item: item[0])
    issue_paths = [Path(issue_path) for _name, issue_path in issue_entries]
    if issue_paths:
        max_workers = min(4, len(issue_paths))
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            chunk_size = (len(issue_paths) + max_workers - 1) // max_workers
            chunks = [
                issue_paths[index : index + chunk_size]
                for index in range(0, len(issue_paths), chunk_size)
            ]
            batches = executor.map(_load_issue_batch, chunks)
            issues = [issue for batch in batches for issue in batch]
        for issue in issues:
            _add_issue_to_index(index, issue)
    return index
