from abc import ABC, abstractmethod
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from typing import Optional
import json


class Pozycja(ABC):
    """Klasa abstrakcyjna reprezentująca pozycję biblioteczną."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str):
        self.tytul = tytul
        self.kod_biblioteczny = kod_biblioteczny
        self._dostepna = True
        self._wypozyczona_do: Optional[datetime] = None
        self._zarezerwowana_przez: Optional[str] = None
        self._data_rezerwacji: Optional[datetime] = None
    
    @property
    def dostepna(self) -> bool:
        """Property sprawdzające dostępność pozycji."""
        return self._dostepna
    
    @property
    def wypozyczona_do(self) -> Optional[datetime]:
        return self._wypozyczona_do
    
    @property
    def zarezerwowana_przez(self) -> Optional[str]:
        return self._zarezerwowana_przez
    
    @abstractmethod
    def get_typ(self) -> str:
        """Metoda abstrakcyjna zwracająca typ pozycji."""
        pass
    
    @abstractmethod
    def get_info(self) -> str:
        """Metoda abstrakcyjna zwracająca szczegółowe informacje."""
        pass
    
    def sprawdz_status(self) -> str:
        """Metoda konkretna - sprawdza status pozycji."""
        if not self._dostepna:
            if self._wypozyczona_do:
                if datetime.now() > self._wypozyczona_do:
                    return f"PRZETRZYMANA (do {self._wypozyczona_do.strftime('%Y-%m-%d')})"
                return f"WYPOŻYCZONA (do {self._wypozyczona_do.strftime('%Y-%m-%d')})"
        if self._zarezerwowana_przez:
            return f"ZAREZERWOWANA przez {self._zarezerwowana_przez}"
        return "DOSTĘPNA"
    
    def wypozycz(self, kod_czytelnika: str, dni: int) -> bool:
        """Metoda do wypożyczania pozycji."""
        if not self._dostepna:
            return False
        if self._zarezerwowana_przez and self._zarezerwowana_przez != kod_czytelnika:
            return False
        
        self._dostepna = False
        self._wypozyczona_do = datetime.now() + timedelta(days=dni)
        self._zarezerwowana_przez = None
        self._data_rezerwacji = None
        return True
    
    def oddaj(self) -> bool:
        """Metoda do oddawania pozycji."""
        if self._dostepna:
            return False
        self._dostepna = True
        self._wypozyczona_do = None
        return True
    
    def zarezerwuj(self, kod_czytelnika: str, dni: int) -> bool:
        """Metoda do rezerwacji pozycji."""
        if not self._dostepna:
            return False
        self._zarezerwowana_przez = kod_czytelnika
        self._data_rezerwacji = datetime.now()
        return True
    
    def anuluj_rezerwacje(self) -> bool:
        """Anuluje rezerwację."""
        if self._zarezerwowana_przez is None:
            return False
        self._zarezerwowana_przez = None
        self._data_rezerwacji = None
        return True
    
    def to_dict(self) -> dict:
        """Konwersja do słownika dla JSON."""
        return {
            'tytul': self.tytul,
            'kod_biblioteczny': self.kod_biblioteczny,
            'dostepna': self._dostepna,
            'wypozyczona_do': self._wypozyczona_do.isoformat() if self._wypozyczona_do else None,
            'zarezerwowana_przez': self._zarezerwowana_przez,
            'data_rezerwacji': self._data_rezerwacji.isoformat() if self._data_rezerwacji else None,
            'typ': self.get_typ()
        }
    
    @classmethod
    def from_dict(cls, data: dict):
        """Tworzenie obiektu ze słownika - do nadpisania w podklasach."""
        return None


class Ksiazka(Pozycja):
    """Klasa reprezentująca książkę."""
    
    def __init__(self, tytul: str, autor: str, wydawnictwo: str, 
                 data_wydania: str, isbn: str, kod_biblioteczny: str):
        super().__init__(tytul, kod_biblioteczny)
        self.autor = autor
        self.wydawnictwo = wydawnictwo
        self.data_wydania = data_wydania
        self.isbn = isbn
    
    def get_typ(self) -> str:
        return "Książka"
    
    def get_info(self) -> str:
        return f"{self.tytul} - {self.autor}, {self.wydawnictwo} ({self.data_wydania})"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'autor': self.autor,
            'wydawnictwo': self.wydawnictwo,
            'data_wydania': self.data_wydania,
            'isbn': self.isbn,
            'klasa': 'Ksiazka'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(
            data['tytul'], data['autor'], data['wydawnictwo'],
            data['data_wydania'], data['isbn'], data['kod_biblioteczny']
        )


class Multimedia(Pozycja):
    """Klasa bazowa dla multimediów."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str, rodzaj: str):
        super().__init__(tytul, kod_biblioteczny)
        self.rodzaj = rodzaj
    
    @abstractmethod
    def get_typ(self) -> str:
        pass


class CD(Multimedia):
    """Klasa reprezentująca płytę CD."""
    
    def __init__(self, tytul: str, wykonawca: str, rok: int, kod_biblioteczny: str):
        super().__init__(tytul, kod_biblioteczny, "CD")
        self.wykonawca = wykonawca
        self.rok = rok
    
    def get_typ(self) -> str:
        return "CD"
    
    def get_info(self) -> str:
        return f"{self.tytul} - {self.wykonawca} ({self.rok})"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'wykonawca': self.wykonawca,
            'rok': self.rok,
            'klasa': 'CD'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['wykonawca'], data['rok'], data['kod_biblioteczny'])


class DVD(Multimedia):
    """Klasa reprezentująca płytę DVD."""
    
    def __init__(self, tytul: str, rezyser: str, rok: int, kod_biblioteczny: str):
        super().__init__(tytul, kod_biblioteczny, "DVD")
        self.rezyser = rezyser
        self.rok = rok
    
    def get_typ(self) -> str:
        return "DVD"
    
    def get_info(self) -> str:
        return f"{self.tytul} - {self.rezyser} ({self.rok})"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'rezyser': self.rezyser,
            'rok': self.rok,
            'klasa': 'DVD'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['rezyser'], data['rok'], data['kod_biblioteczny'])


class Kaseta(Multimedia):
    """Klasa reprezentująca kasetę magnetofonową."""
    
    def __init__(self, tytul: str, wykonawca: str, rok: int, kod_biblioteczny: str):
        super().__init__(tytul, kod_biblioteczny, "Kaseta")
        self.wykonawca = wykonawca
        self.rok = rok
    
    def get_typ(self) -> str:
        return "Kaseta"
    
    def get_info(self) -> str:
        return f"{self.tytul} - {self.wykonawca} ({self.rok})"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'wykonawca': self.wykonawca,
            'rok': self.rok,
            'klasa': 'Kaseta'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['wykonawca'], data['rok'], data['kod_biblioteczny'])


class Czasopismo(Pozycja):
    """Klasa bazowa dla czasopism."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str, okres: str):
        super().__init__(tytul, kod_biblioteczny)
        self.okres = okres
    
    @abstractmethod
    def get_typ(self) -> str:
        pass


class Dziennik(Czasopismo):
    """Klasa reprezentująca dziennik."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str, data_wydania: str):
        super().__init__(tytul, kod_biblioteczny, "dziennik")
        self.data_wydania = data_wydania
    
    def get_typ(self) -> str:
        return "Dziennik"
    
    def get_info(self) -> str:
        return f"{self.tytul} ({self.data_wydania})"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'data_wydania': self.data_wydania,
            'klasa': 'Dziennik'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['kod_biblioteczny'], data['data_wydania'])


class Tygodnik(Czasopismo):
    """Klasa reprezentująca tygodnik."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str, numer: int, rok: int):
        super().__init__(tytul, kod_biblioteczny, "tygodnik")
        self.numer = numer
        self.rok = rok
    
    def get_typ(self) -> str:
        return "Tygodnik"
    
    def get_info(self) -> str:
        return f"{self.tytul} nr {self.numer}/{self.rok}"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'numer': self.numer,
            'rok': self.rok,
            'klasa': 'Tygodnik'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['kod_biblioteczny'], data['numer'], data['rok'])


class Miesiecznik(Czasopismo):
    """Klasa reprezentująca miesięcznik."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str, numer: int, rok: int):
        super().__init__(tytul, kod_biblioteczny, "miesięcznik")
        self.numer = numer
        self.rok = rok
    
    def get_typ(self) -> str:
        return "Miesięcznik"
    
    def get_info(self) -> str:
        return f"{self.tytul} nr {self.numer}/{self.rok}"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'numer': self.numer,
            'rok': self.rok,
            'klasa': 'Miesiecznik'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['kod_biblioteczny'], data['numer'], data['rok'])


class GraPlanszowa(Pozycja):
    """Klasa reprezentująca grę planszową."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str, wydawca: str, min_graczy: int, max_graczy: int):
        super().__init__(tytul, kod_biblioteczny)
        self.wydawca = wydawca
        self.min_graczy = min_graczy
        self.max_graczy = max_graczy
    
    def get_typ(self) -> str:
        return "Gra planszowa"
    
    def get_info(self) -> str:
        return f"{self.tytul} - {self.wydawca} ({self.min_graczy}-{self.max_graczy} graczy)"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'wydawca': self.wydawca,
            'min_graczy': self.min_graczy,
            'max_graczy': self.max_graczy,
            'klasa': 'GraPlanszowa'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['kod_biblioteczny'], data['wydawca'],
                   data['min_graczy'], data['max_graczy'])


class ZasobyCyfrowe(Pozycja):
    """Klasa reprezentująca zasoby cyfrowe."""
    
    def __init__(self, tytul: str, kod_biblioteczny: str, format: str, rozmiar_mb: int):
        super().__init__(tytul, kod_biblioteczny)
        self.format = format
        self.rozmiar_mb = rozmiar_mb
    
    def get_typ(self) -> str:
        return "Zasoby cyfrowe"
    
    def get_info(self) -> str:
        return f"{self.tytul} - {self.format} ({self.rozmiar_mb} MB)"
    
    def to_dict(self) -> dict:
        data = super().to_dict()
        data.update({
            'format': self.format,
            'rozmiar_mb': self.rozmiar_mb,
            'klasa': 'ZasobyCyfrowe'
        })
        return data
    
    @classmethod
    def from_dict(cls, data: dict):
        return cls(data['tytul'], data['kod_biblioteczny'], data['format'], data['rozmiar_mb'])


class KolekcjaPozycji:
    """Klasa reprezentująca kolekcję pozycji z iteratorem."""
    
    def __init__(self):
        self._pozycje: list[Pozycja] = []
    
    def dodaj(self, pozycja: Pozycja):
        self._pozycje.append(pozycja)
    
    def usun(self, kod_biblioteczny: str) -> bool:
        for i, p in enumerate(self._pozycje):
            if p.kod_biblioteczny == kod_biblioteczny:
                self._pozycje.pop(i)
                return True
        return False
    
    def znajdz(self, kod_biblioteczny: str) -> Optional[Pozycja]:
        for p in self._pozycje:
            if p.kod_biblioteczny == kod_biblioteczny:
                return p
        return None
    
    def __iter__(self):
        return iter(self._pozycje)
    
    def __len__(self):
        return len(self._pozycje)
    
    def generuj_kod(self) -> str:
        """Generator yield do generowania kodów bibliotecznych."""
        max_kod = 0
        for p in self._pozycje:
            try:
                kod_num = int(p.kod_biblioteczny.replace("BIB-", ""))
                if kod_num > max_kod:
                    max_kod = kod_num
            except:
                pass
        yield f"BIB-{max_kod + 1}"
    
    def wyszukaj_tytul(self, fraza: str) -> list[Pozycja]:
        """Wyszukuje pozycje po tytule (generator)."""
        for p in self._pozycje:
            if fraza.lower() in p.tytul.lower():
                yield p