"""
Linked List Node Strategy Implementation

This module implements the LINKED_LIST strategy for efficient
insertions and deletions with sequential access patterns.
"""

from typing import Any, Iterator, List, Dict, Optional
from .base import ANodeLinearStrategy
from ...defs import NodeMode, NodeTrait
from .contracts import NodeType


class ListNode:
    """Node in the doubly linked list."""
    
    def __init__(self, key: str, value: Any):
        self.key = key
        self.value = value
        self.prev: Optional['ListNode'] = None
        self.next: Optional['ListNode'] = None


class LinkedListStrategy(ANodeLinearStrategy):
    """
    Linked List node strategy for efficient insertions and deletions.
    
    Provides O(1) insertions/deletions at known positions with
    sequential access patterns optimized for iteration.
    """
    
    # Strategy type classification
    STRATEGY_TYPE = NodeType.LINEAR

    
    def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
        """Initialize the Linked List strategy."""
        super().__init__(NodeMode.LINKED_LIST, traits, **options)
        
        self.doubly_linked = options.get('doubly_linked', True)
        
        # Doubly linked list with sentinel nodes
        self._head = ListNode("HEAD", None)
        self._tail = ListNode("TAIL", None)
        self._head.next = self._tail
        self._tail.prev = self._head
        
        # Quick access mapping
        self._key_to_node: Dict[str, ListNode] = {}
        self._size = 0
    
    def get_supported_traits(self) -> NodeTrait:
        """Get the traits supported by the linked list strategy."""
        return (NodeTrait.ORDERED | NodeTrait.INDEXED)
    
    def _insert_after(self, prev_node: ListNode, key: str, value: Any) -> ListNode:
        """Insert new node after given node."""
        new_node = ListNode(key, value)
        next_node = prev_node.next
        
        # Link new node
        prev_node.next = new_node
        new_node.prev = prev_node
        new_node.next = next_node
        next_node.prev = new_node
        
        return new_node
    
    def _remove_node(self, node: ListNode) -> None:
        """Remove node from list."""
        prev_node = node.prev
        next_node = node.next
        
        prev_node.next = next_node
        next_node.prev = prev_node
        
        # Clear references
        node.prev = None
        node.next = None
    
    def _get_node_at_index(self, index: int) -> Optional[ListNode]:
        """Get node at given index."""
        if index < 0 or index >= self._size:
            return None
        
        # Optimize direction based on index
        if index < self._size // 2:
            # Search from head
            current = self._head.next
            for _ in range(index):
                current = current.next
        else:
            # Search from tail
            current = self._tail.prev
            for _ in range(self._size - index - 1):
                current = current.prev
        
        return current if current != self._head and current != self._tail else None
    
    # ============================================================================
    # CORE OPERATIONS (Key-based interface for compatibility)
    # ============================================================================
    
    def put(self, key: Any, value: Any = None) -> None:
        """Add/update key-value pair."""
        key_str = str(key)
        
        if key_str in self._key_to_node:
            # Update existing
            self._key_to_node[key_str].value = value
        else:
            # Insert at end
            new_node = self._insert_after(self._tail.prev, key_str, value)
            self._key_to_node[key_str] = new_node
            self._size += 1
    
    def get(self, key: Any, default: Any = None) -> Any:
        """Get value by key."""
        key_str = str(key)
        
        if key_str == "list_info":
            return {
                'size': self._size,
                'doubly_linked': self.doubly_linked,
                'first': self.first(),
                'last': self.last()
            }
        elif key_str.isdigit():
            # Numeric access by index
            index = int(key_str)
            node = self._get_node_at_index(index)
            return node.value if node else default
        
        node = self._key_to_node.get(key_str)
        return node.value if node else default
    
    def has(self, key: Any) -> bool:
        """Check if key exists."""
        key_str = str(key)
        
        if key_str == "list_info":
            return True
        elif key_str.isdigit():
            index = int(key_str)
            return 0 <= index < self._size
        
        return key_str in self._key_to_node
    
    def remove(self, key: Any) -> bool:
        """Remove key from list."""
        key_str = str(key)
        
        if key_str.isdigit():
            # Remove by index
            index = int(key_str)
            node = self._get_node_at_index(index)
            if node:
                self._remove_node(node)
                del self._key_to_node[node.key]
                self._size -= 1
                return True
            return False
        
        node = self._key_to_node.get(key_str)
        if node:
            self._remove_node(node)
            del self._key_to_node[key_str]
            self._size -= 1
            return True
        
        return False
    
    def delete(self, key: Any) -> bool:
        """Remove key from list (alias for remove)."""
        return self.remove(key)
    
    def clear(self) -> None:
        """Clear all data."""
        self._head.next = self._tail
        self._tail.prev = self._head
        self._key_to_node.clear()
        self._size = 0
    
    def keys(self) -> Iterator[str]:
        """Get all keys in insertion order."""
        current = self._head.next
        while current != self._tail:
            yield current.key
            current = current.next
    
    def values(self) -> Iterator[Any]:
        """Get all values in insertion order."""
        current = self._head.next
        while current != self._tail:
            yield current.value
            current = current.next
    
    def items(self) -> Iterator[tuple[str, Any]]:
        """Get all key-value pairs in insertion order."""
        current = self._head.next
        while current != self._tail:
            yield (current.key, current.value)
            current = current.next
    
    def __len__(self) -> int:
        """Get number of elements."""
        return self._size
    
    def to_native(self) -> List[Any]:
        """Convert to native Python list."""
        return list(self.values())
    
    @property
    def is_list(self) -> bool:
        """This is a list strategy."""
        return True
    
    @property
    def is_dict(self) -> bool:
        """This also behaves like a dict."""
        return True
    
    # ============================================================================
    # LINKED LIST SPECIFIC OPERATIONS
    # ============================================================================
    
    def append(self, value: Any) -> str:
        """Append value to end of list."""
        key = str(self._size)
        self.put(key, value)
        return key
    
    def prepend(self, value: Any) -> str:
        """Prepend value to beginning of list."""
        key = f"prepend_{self._size}"
        new_node = self._insert_after(self._head, key, value)
        self._key_to_node[key] = new_node
        self._size += 1
        return key
    
    def insert_at(self, index: int, value: Any) -> str:
        """Insert value at specific index."""
        if index < 0 or index > self._size:
            raise IndexError(f"Index {index} out of range")
        
        if index == 0:
            return self.prepend(value)
        elif index == self._size:
            return self.append(value)
        
        # Find insertion point
        prev_node = self._get_node_at_index(index - 1)
        if not prev_node:
            raise IndexError(f"Cannot find insertion point at index {index}")
        
        key = f"insert_{index}_{self._size}"
        new_node = self._insert_after(prev_node, key, value)
        self._key_to_node[key] = new_node
        self._size += 1
        return key
    
    def insert(self, index: int, value: Any) -> None:
        """Insert value at index."""
        self.insert_at(index, value)
    
    def push_back(self, value: Any) -> None:
        """Add element to back."""
        self.append(value)
    
    def push_front(self, value: Any) -> None:
        """Add element to front."""
        self.prepend(value)
    
    def pop_back(self) -> Any:
        """Remove and return element from back."""
        return self.pop()
    
    def pop_front(self) -> Any:
        """Remove and return element from front."""
        return self.popleft()
    
    def pop(self) -> Any:
        """Remove and return last element."""
        if self._size == 0:
            raise IndexError("pop from empty list")
        
        last_node = self._tail.prev
        value = last_node.value
        self._remove_node(last_node)
        del self._key_to_node[last_node.key]
        self._size -= 1
        return value
    
    def popleft(self) -> Any:
        """Remove and return first element."""
        if self._size == 0:
            raise IndexError("popleft from empty list")
        
        first_node = self._head.next
        value = first_node.value
        self._remove_node(first_node)
        del self._key_to_node[first_node.key]
        self._size -= 1
        return value
    
    def first(self) -> Any:
        """Get first element without removing."""
        if self._size == 0:
            return None
        return self._head.next.value
    
    def last(self) -> Any:
        """Get last element without removing."""
        if self._size == 0:
            return None
        return self._tail.prev.value
    
    def reverse(self) -> None:
        """Reverse the list in place."""
        if self._size <= 1:
            return
        
        # Swap all next/prev pointers
        current = self._head
        while current:
            current.next, current.prev = current.prev, current.next
            current = current.prev  # Note: we swapped, so prev is now next
        
        # Swap head and tail
        self._head, self._tail = self._tail, self._head
    
    def get_at_index(self, index: int) -> Any:
        """Get value at specific index."""
        node = self._get_node_at_index(index)
        if not node:
            raise IndexError(f"Index {index} out of range")
        return node.value
    
    def set_at_index(self, index: int, value: Any) -> None:
        """Set value at specific index."""
        node = self._get_node_at_index(index)
        if not node:
            raise IndexError(f"Index {index} out of range")
        node.value = value
    
    def find_index(self, value: Any) -> int:
        """Find index of first occurrence of value."""
        current = self._head.next
        index = 0
        
        while current != self._tail:
            if current.value == value:
                return index
            current = current.next
            index += 1
        
        return -1
    
    def remove_value(self, value: Any) -> bool:
        """Remove first occurrence of value."""
        current = self._head.next
        
        while current != self._tail:
            if current.value == value:
                self._remove_node(current)
                del self._key_to_node[current.key]
                self._size -= 1
                return True
            current = current.next
        
        return False
    
    def count_value(self, value: Any) -> int:
        """Count occurrences of value."""
        count = 0
        current = self._head.next
        
        while current != self._tail:
            if current.value == value:
                count += 1
            current = current.next
        
        return count
    
    def to_array(self) -> List[Any]:
        """Convert to array representation."""
        return list(self.values())
    
    def get_statistics(self) -> Dict[str, Any]:
        """Get comprehensive linked list statistics."""
        return {
            'size': self._size,
            'doubly_linked': self.doubly_linked,
            'first_value': self.first(),
            'last_value': self.last(),
            'memory_overhead': self._size * (64 if self.doubly_linked else 32),  # Pointer overhead
            'access_pattern': 'sequential'
        }
    
    # ============================================================================
    # PERFORMANCE CHARACTERISTICS
    # ============================================================================
    
    @property
    def backend_info(self) -> Dict[str, Any]:
        """Get backend implementation info."""
        return {
            'strategy': 'LINKED_LIST',
            'backend': f'{"Doubly" if self.doubly_linked else "Singly"} linked list with sentinel nodes',
            'doubly_linked': self.doubly_linked,
            'complexity': {
                'append': 'O(1)',
                'prepend': 'O(1)',
                'insert_at': 'O(n)',
                'remove_at': 'O(n)',
                'access_by_index': 'O(n)',
                'search': 'O(n)',
                'space': 'O(n)'
            }
        }
    
    @property
    def metrics(self) -> Dict[str, Any]:
        """Get performance metrics."""
        stats = self.get_statistics()
        
        return {
            'size': stats['size'],
            'first_value': str(stats['first_value']) if stats['first_value'] is not None else 'None',
            'last_value': str(stats['last_value']) if stats['last_value'] is not None else 'None',
            'doubly_linked': stats['doubly_linked'],
            'memory_overhead': f"{stats['memory_overhead']} bytes",
            'access_pattern': stats['access_pattern']
        }
