from abc import ABC, abstractmethod
from typing import List, Dict, Any


class AssemblyStrategy(ABC):
    """
    Abstract base class for assembly strategies.

    Each strategy defines how to assemble knowledge chunks for a specific target AI assistant.
    """

    @abstractmethod
    def assemble(self, chunks: List, token_budget: int, quality_threshold: float, verbose: bool = False) -> str:
        """
        Assemble chunks into the target-specific format.

        Args:
            chunks: List of KnowledgeChunk objects
            token_budget: Maximum token budget
            quality_threshold: Minimum quality threshold
            verbose: Whether to show all metadata and chunk provenance information

        Returns:
            Assembled content formatted for the target
        """
        pass

    def _count_tokens(self, text: str) -> int:
        """Count tokens in text using simple word splitting."""
        return len(text.split())

    def _get_chunk_tag(self, chunk, default: str = "Information") -> str:
        """Get the first tag from chunk metadata or return default."""
        return chunk.metadata.tags[0] if chunk.metadata.tags else default

    def _check_token_budget(self, current_tokens: int, chunk_tokens: int, token_budget: int) -> bool:
        """Check if adding chunk would exceed token budget (with 10% buffer)."""
        return current_tokens + chunk_tokens <= token_budget * 0.9


class CopilotAssemblyStrategy(AssemblyStrategy):
    """Assembly strategy for GitHub Copilot."""

    def assemble(self, chunks: List, token_budget: int, quality_threshold: float, verbose: bool = False) -> str:
        if verbose:
            content = "# GitHub Copilot Instructions\n\n"
            content += "## Project Context\n\n"
        else:
            content = "# Instructions\n\n"

        current_tokens = self._count_tokens(content)

        # Group chunks by domain for better organization
        domain_groups = self._group_chunks_by_domain(chunks)

        # Add chunks by domain
        for domain, domain_chunks in domain_groups.items():
            if current_tokens >= token_budget * 0.9:
                break

            if verbose:
                content += f"### {domain.title()} Guidelines\n\n"
            # else:
            #     content += f"## {domain.title()}\n\n"

            for chunk in domain_chunks:
                chunk_tokens = self._count_tokens(chunk.content)
                if not self._check_token_budget(current_tokens, chunk_tokens, token_budget):
                    break

                if verbose:
                    authority_indicator = self._get_authority_indicator(chunk.metadata.authority.value)
                    tag = self._get_chunk_tag(chunk, 'Guideline')
                    content += f"#### {authority_indicator}: {tag}\n\n"
                # else:
                    # tag = self._get_chunk_tag(chunk, 'Guideline')
                    # content += f"### {tag}\n\n"

                content += chunk.content + "\n\n"
                current_tokens += chunk_tokens

        if verbose:
            content += "\n---\n"
            content += f"*Generated by Context Mixer CRAFT system - {len(chunks)} chunks processed*\n"

        return content

    def _group_chunks_by_domain(self, chunks: List) -> Dict[str, List]:
        """Group chunks by domain."""
        domain_groups = {}
        for chunk in chunks:
            for domain in chunk.metadata.domains:
                if domain not in domain_groups:
                    domain_groups[domain] = []
                domain_groups[domain].append(chunk)
        return domain_groups

    def _get_authority_indicator(self, authority_value: str) -> str:
        """Get authority indicator emoji and text."""
        indicators = {
            'foundational': '🏛️ FOUNDATIONAL',
            'official': '🔒 OFFICIAL',
            'conventional': '📋 CONVENTIONAL',
            'experimental': '🧪 EXPERIMENTAL',
            'deprecated': '⚠️ DEPRECATED'
        }
        return indicators.get(authority_value, '📝 STANDARD')


class ClaudeAssemblyStrategy(AssemblyStrategy):
    """Assembly strategy for Claude."""

    def assemble(self, chunks: List, token_budget: int, quality_threshold: float, verbose: bool = False) -> str:
        if verbose:
            content = "# Claude Context Instructions\n\n"
            content += "You are an AI assistant with the following project context:\n\n"
        else:
            content = "# Context\n\n"

        current_tokens = self._count_tokens(content)

        for i, chunk in enumerate(chunks, 1):
            chunk_tokens = self._count_tokens(chunk.content)
            if not self._check_token_budget(current_tokens, chunk_tokens, token_budget):
                break

            if verbose:
                tag = self._get_chunk_tag(chunk, 'Information')
                content += f"## Context {i}: {tag}\n\n"
                content += f"**Authority Level:** {chunk.metadata.authority.value.title()}\n"
                content += f"**Domains:** {', '.join(chunk.metadata.domains)}\n\n"
            # else:
                # tag = self._get_chunk_tag(chunk, 'Information')
                # content += f"## {tag}\n\n"

            content += chunk.content + "\n\n"
            current_tokens += chunk_tokens

        return content


class CursorAssemblyStrategy(AssemblyStrategy):
    """Assembly strategy for Cursor."""

    def assemble(self, chunks: List, token_budget: int, quality_threshold: float, verbose: bool = False) -> str:
        if verbose:
            content = "# Cursor AI Context\n\n"
            content += "## Development Guidelines\n\n"
        else:
            content = "# Guidelines\n\n"

        current_tokens = self._count_tokens(content)

        for chunk in chunks:
            chunk_tokens = self._count_tokens(chunk.content)
            if not self._check_token_budget(current_tokens, chunk_tokens, token_budget):
                break

            if verbose:
                tag = self._get_chunk_tag(chunk, 'Guideline')
                content += f"### {tag}\n\n"
            # else:
                # tag = self._get_chunk_tag(chunk, 'Guideline')
                # content += f"## {tag}\n\n"

            content += chunk.content + "\n\n"
            current_tokens += chunk_tokens

        return content


class GenericAssemblyStrategy(AssemblyStrategy):
    """Generic assembly strategy."""

    def assemble(self, chunks: List, token_budget: int, quality_threshold: float, verbose: bool = False) -> str:
        if verbose:
            content = "# Assembled Context\n\n"
        else:
            content = "# Context\n\n"

        current_tokens = self._count_tokens(content)

        for i, chunk in enumerate(chunks, 1):
            chunk_tokens = self._count_tokens(chunk.content)
            if not self._check_token_budget(current_tokens, chunk_tokens, token_budget):
                break

            if verbose:
                content += f"## Section {i}\n\n"
                content += f"**ID:** {chunk.id}\n"
                content += f"**Authority:** {chunk.metadata.authority.value}\n"
                content += f"**Domains:** {', '.join(chunk.metadata.domains)}\n"
                content += f"**Tags:** {', '.join(chunk.metadata.tags)}\n\n"
            # else:
                # tag = self._get_chunk_tag(chunk, f"Section {i}")
                # content += f"## {tag}\n\n"

            content += chunk.content + "\n\n"
            current_tokens += chunk_tokens

        return content


class AssemblyStrategyFactory:
    """Factory for creating assembly strategies."""

    _strategies = {
        'copilot': CopilotAssemblyStrategy,
        'claude': ClaudeAssemblyStrategy,
        'cursor': CursorAssemblyStrategy,
        'generic': GenericAssemblyStrategy,
    }

    @classmethod
    def create_strategy(cls, target: str) -> AssemblyStrategy:
        """
        Create an assembly strategy for the given target.

        Args:
            target: Target AI assistant name

        Returns:
            AssemblyStrategy instance for the target
        """
        target_lower = target.lower()
        strategy_class = cls._strategies.get(target_lower, GenericAssemblyStrategy)
        return strategy_class()

    @classmethod
    def get_supported_targets(cls) -> List[str]:
        """Get list of supported target names."""
        return list(cls._strategies.keys())
