import json
from datetime import datetime, timedelta
from typing import Optional
from osoby import Pracownik, Czytelnik, KolekcjaPracownikow, KolekcjaCzytelnikow, dekorator_limit
from pozycje import (
    Pozycja, Ksiazka, CD, DVD, Kaseta, 
    Dziennik, Tygodnik, Miesiecznik, GraPlanszowa, ZasobyCyfrowe, KolekcjaPozycji
)


class Konfiguracja:
    """Klasa przechowująca konfigurację biblioteki."""
    
    max_wypozyczenia = 5
    dni_wypozyczenia = 14
    dni_rezerwacji = 3
    
    @classmethod
    def ustaw_limit(cls, limit: int):
        cls.max_wypozyczenia = limit
    
    @classmethod
    def ustaw_dni_wypozyczenia(cls, dni: int):
        cls.dni_wypozyczenia = dni
    
    @classmethod
    def ustaw_dni_rezerwacji(cls, dni: int):
        cls.dni_rezerwacji = dni
    
    @classmethod
    def to_dict(cls) -> dict:
        return {
            'max_wypozyczenia': cls.max_wypozyczenia,
            'dni_wypozyczenia': cls.dni_wypozyczenia,
            'dni_rezerwacji': cls.dni_rezerwacji
        }
    
    @classmethod
    def from_dict(cls, data: dict):
        cls.max_wypozyczenia = data.get('max_wypozyczenia', 5)
        cls.dni_wypozyczenia = data.get('dni_wypozyczenia', 14)
        cls.dni_rezerwacji = data.get('dni_rezerwacji', 3)


class Biblioteka:
    """Główna klasa zarządzająca biblioteką."""
    
    def __init__(self):
        self.pozycje = KolekcjaPozycji()
        self.pracownicy = KolekcjaPracownikow()
        self.czytelnicy = KolekcjaCzytelnikow()
    
    def dodaj_pracownika(self, imie: str, nazwisko: str, stanowisko: str, 
                         data_zatrudnienia: str, pesel: str = "", telefon: str = "") -> Pracownik:
        p = Pracownik(imie, nazwisko, stanowisko, data_zatrudnienia, pesel, telefon)
        return self.pracownicy.dodaj(p)
    
    def usun_pracownika(self, id_pracownika: int) -> bool:
        return self.pracownicy.usun(id_pracownika)
    
    def modyfikuj_pracownika(self, id_pracownika: int, **kwargs) -> bool:
        return self.pracownicy.modyfikuj(id_pracownika, **kwargs)
    
    def dodaj_czytelnika(self, imie: str, nazwisko: str, adres: str, 
                         telefon: str, email: str = "") -> Czytelnik:
        c = Czytelnik(imie, nazwisko, adres, telefon, email)
        return self.czytelnicy.dodaj(c)
    
    def usun_czytelnika(self, id_czytelnika: int) -> bool:
        czytelnik = self.czytelnicy.znajdz(id_czytelnika)
        if czytelnik and (czytelnik.ilosc_wypozyczonych > 0 or czytelnik._zarezerwowane):
            return False
        return self.czytelnicy.usun(id_czytelnika)
    
    def modyfikuj_czytelnika(self, id_czytelnika: int, **kwargs) -> bool:
        return self.czytelnicy.modyfikuj(id_czytelnika, **kwargs)
    
    def dodaj_ksiazke(self, tytul: str, autor: str, wydawnictwo: str, 
                      data_wydania: str, isbn: str, kod_biblioteczny: str) -> Ksiazka:
        k = Ksiazka(tytul, autor, wydawnictwo, data_wydania, isbn, kod_biblioteczny)
        self.pozycje.dodaj(k)
        return k
    
    def dodaj_cd(self, tytul: str, wykonawca: str, rok: int, kod_biblioteczny: str) -> CD:
        c = CD(tytul, wykonawca, rok, kod_biblioteczny)
        self.pozycje.dodaj(c)
        return c
    
    def dodaj_dvd(self, tytul: str, rezyser: str, rok: int, kod_biblioteczny: str) -> DVD:
        d = DVD(tytul, rezyser, rok, kod_biblioteczny)
        self.pozycje.dodaj(d)
        return d
    
    def dodaj_kasete(self, tytul: str, wykonawca: str, rok: int, kod_biblioteczny: str) -> Kaseta:
        k = Kaseta(tytul, wykonawca, rok, kod_biblioteczny)
        self.pozycje.dodaj(k)
        return k
    
    def dodaj_dziennik(self, tytul: str, kod_biblioteczny: str, data_wydania: str) -> Dziennik:
        d = Dziennik(tytul, kod_biblioteczny, data_wydania)
        self.pozycje.dodaj(d)
        return d
    
    def dodaj_tygodnik(self, tytul: str, kod_biblioteczny: str, numer: int, rok: int) -> Tygodnik:
        t = Tygodnik(tytul, kod_biblioteczny, numer, rok)
        self.pozycje.dodaj(t)
        return t
    
    def dodaj_miesiecznik(self, tytul: str, kod_biblioteczny: str, numer: int, rok: int) -> Miesiecznik:
        m = Miesiecznik(tytul, kod_biblioteczny, numer, rok)
        self.pozycje.dodaj(m)
        return m
    
    def dodaj_gre_planszowa(self, tytul: str, kod_biblioteczny: str, wydawca: str, 
                            min_graczy: int, max_graczy: int) -> GraPlanszowa:
        g = GraPlanszowa(tytul, kod_biblioteczny, wydawca, min_graczy, max_graczy)
        self.pozycje.dodaj(g)
        return g
    
    def dodaj_zasoby_cyfrowe(self, tytul: str, kod_biblioteczny: str, 
                             format: str, rozmiar_mb: int) -> ZasobyCyfrowe:
        z = ZasobyCyfrowe(tytul, kod_biblioteczny, format, rozmiar_mb)
        self.pozycje.dodaj(z)
        return z
    
    def usun_pozycje(self, kod_biblioteczny: str) -> bool:
        pozycja = self.pozycje.znajdz(kod_biblioteczny)
        if pozycja and not pozycja.dostepna:
            return False
        return self.pozycje.usun(kod_biblioteczny)
    
    def wypozycz(self, id_czytelnika: int, kod_biblioteczny: str) -> str:
        """Wypożycz pozycję czytelnikowi (przez ID)."""
        pozycja = self.pozycje.znajdz(kod_biblioteczny)
        if not pozycja:
            return "Błąd: Nie znaleziono pozycji"
        
        czytelnik = self.czytelnicy.znajdz(id_czytelnika)
        if not czytelnik:
            return "Błąd: Nie znaleziono czytelnika"
        
        if czytelnik.ilosc_wypozyczonych >= Konfiguracja.max_wypozyczenia:
            return f"Błąd: Osiągnięto limit {Konfiguracja.max_wypozyczenia} wypożyczeń"
        
        id_str = str(id_czytelnika)
        
        if pozycja.wypozycz(id_str, Konfiguracja.dni_wypozyczenia):
            czytelnik.dodaj_wypozyczenie(kod_biblioteczny)
            return f"OK: Wypożyczono '{pozycja.tytul}' na {Konfiguracja.dni_wypozyczenia} dni"
        
        return f"Błąd: Pozycja '{pozycja.tytul}' nie jest dostępna"
    
    def oddaj(self, kod_biblioteczny: str) -> str:
        pozycja = self.pozycje.znajdz(kod_biblioteczny)
        if not pozycja:
            return "Błąd: Nie znaleziono pozycji"
        
        if pozycja.oddaj():
            for czytelnik in self.czytelnicy:
                if kod_biblioteczny in czytelnik._wypozyczone:
                    czytelnik.usun_wypozyczenie(kod_biblioteczny)
                    return f"OK: Oddano '{pozycja.tytul}'"
        
        return "Błąd: Pozycja nie była wypożyczona"
    
    def zarezerwuj(self, id_czytelnika: int, kod_biblioteczny: str) -> str:
        czytelnik = self.czytelnicy.znajdz(id_czytelnika)
        if not czytelnik:
            return "Błąd: Nie znaleziono czytelnika"
        
        pozycja = self.pozycje.znajdz(kod_biblioteczny)
        if not pozycja:
            return "Błąd: Nie znaleziono pozycji"
        
        id_str = str(id_czytelnika)
        
        if pozycja.zarezerwuj(id_str, Konfiguracja.dni_rezerwacji):
            czytelnik.dodaj_rezerwacje(kod_biblioteczny)
            return f"OK: Zarezerwowano '{pozycja.tytul}' na {Konfiguracja.dni_rezerwacji} dni"
        
        return "Błąd: Pozycja nie jest dostępna do rezerwacji"
    
    def anuluj_rezerwacje(self, id_czytelnika: int, kod_biblioteczny: str) -> str:
        czytelnik = self.czytelnicy.znajdz(id_czytelnika)
        if not czytelnik:
            return "Błąd: Nie znaleziono czytelnika"
        
        pozycja = self.pozycje.znajdz(kod_biblioteczny)
        if not pozycja:
            return "Błąd: Nie znaleziono pozycji"
        
        if pozycja.anuluj_rezerwacje():
            czytelnik.usun_rezerwacje(kod_biblioteczny)
            return f"OK: Anulowano rezerwację '{pozycja.tytul}'"
        
        return "Błąd: Pozycja nie była zarezerwowana"
    
    def kontrola_wypozyczen(self) -> dict:
        wynik = {
            'wypozyczone': [],
            'zarezerwowane': [],
            'przetrzymane': []
        }
        
        for pozycja in self.pozycje:
            status = pozycja.sprawdz_status()
            if 'WYPOŻYCZONA' in status or 'PRZETRZYMANA' in status:
                for czytelnik in self.czytelnicy:
                    if pozycja.kod_biblioteczny in czytelnik._wypozyczone:
                        wynik['wypozyczone'].append({
                            'pozycja': pozycja.tytul,
                            'czytelnik': czytelnik.pelne_dane,
                            'status': status
                        })
                        if 'PRZETRZYMANA' in status:
                            wynik['przetrzymane'].append({
                                'pozycja': pozycja.tytul,
                                'czytelnik': czytelnik.pelne_dane
                            })
            elif 'ZAREZERWOWANA' in status:
                for czytelnik in self.czytelnicy:
                    if pozycja.kod_biblioteczny in czytelnik._zarezerwowane:
                        wynik['zarezerwowane'].append({
                            'pozycja': pozycja.tytul,
                            'czytelnik': czytelnik.pelne_dane
                        })
        
        return wynik
    
    def zapisz_json(self, nazwa_pliku: str = "biblioteka.json"):
        dane = {
            'konfiguracja': Konfiguracja.to_dict(),
            'pracownicy': [p.to_dict() for p in self.pracownicy],
            'czytelnicy': [c.to_dict() for c in self.czytelnicy],
            'pozycje': []
        }
        
        for pozycja in self.pozycje:
            dane['pozycje'].append(pozycja.to_dict())
        
        with open(nazwa_pliku, 'w', encoding='utf-8') as f:
            json.dump(dane, f, ensure_ascii=False, indent=2)
        
        return f"Zapisano do pliku {nazwa_pliku}"
    
    def wczytaj_json(self, nazwa_pliku: str = "biblioteka.json"):
        try:
            with open(nazwa_pliku, 'r', encoding='utf-8') as f:
                dane = json.load(f)
        except FileNotFoundError:
            return "Błąd: Plik nie istnieje"
        
        if 'konfiguracja' in dane:
            Konfiguracja.from_dict(dane['konfiguracja'])
        
        self.pracownicy = KolekcjaPracownikow()
        for p_data in dane.get('pracownicy', []):
            p = Pracownik.from_dict(p_data)
            self.pracownicy.dodaj(p)
        
        self.czytelnicy = KolekcjaCzytelnikow()
        for c_data in dane.get('czytelnicy', []):
            c = Czytelnik.from_dict(c_data)
            self.czytelnicy.dodaj(c)
        
        self.pozycje = KolekcjaPozycji()
        klasy_map = {
            'Ksiazka': Ksiazka,
            'CD': CD,
            'DVD': DVD,
            'Kaseta': Kaseta,
            'Dziennik': Dziennik,
            'Tygodnik': Tygodnik,
            'Miesiecznik': Miesiecznik,
            'GraPlanszowa': GraPlanszowa,
            'ZasobyCyfrowe': ZasobyCyfrowe
        }
        
        for p_data in dane.get('pozycje', []):
            klasa = klasy_map.get(p_data.get('klasa'))
            if klasa:
                pozycja = klasa.from_dict(p_data)
                if pozycja._wypozyczona_do:
                    pozycja._wypozyczona_do = datetime.fromisoformat(pozycja._wypozyczona_do)
                if pozycja._data_rezerwacji:
                    pozycja._data_rezerwacji = datetime.fromisoformat(pozycja._data_rezerwacji)
                self.pozycje.dodaj(pozycja)
        
        return f"Wczytano z pliku {nazwa_pliku}"
    
    def generuj_raport(self) -> str:
        raport = []
        raport.append("=" * 50)
        raport.append("RAPORT BIBLIOTeki")
        raport.append("=" * 50)
        
        raport.append(f"\nPRACOWNICY ({len(self.pracownicy)}):")
        for p in self.pracownicy:
            raport.append(f"  {p.id_pracownika}: {p.pelne_dane} - {p.stanowisko}")
        
        raport.append(f"\nCZYTELNICY ({len(self.czytelnicy)}):")
        for c in self.czytelnicy:
            raport.append(f"  {c.id_czytelnika}: {c.pelne_dane} - {c.ilosc_wypozyczonych} wypożyczonych")
        
        raport.append(f"\nPOZYCJE ({len(self.pozycje)}):")
        for p in self.pozycje:
            raport.append(f"  {p.kod_biblioteczny}: {p.get_typ()} - {p.tytul} [{p.sprawdz_status()}]")
        
        return "\n".join(raport)
    
    def zapisz_pozycje(self, nazwa_pliku: str = "pozycje.json") -> str:
        dane = [pozycja.to_dict() for pozycja in self.pozycje]
        with open(nazwa_pliku, 'w', encoding='utf-8') as f:
            json.dump(dane, f, ensure_ascii=False, indent=2)
        return f"Zapisano {len(dane)} pozycji do pliku {nazwa_pliku}"
    
    def wczytaj_pozycje(self, nazwa_pliku: str = "pozycje.json") -> str:
        try:
            with open(nazwa_pliku, 'r', encoding='utf-8') as f:
                dane = json.load(f)
        except FileNotFoundError:
            return "Błąd: Plik nie istnieje"
        
        self.pozycje = KolekcjaPozycji()
        klasy_map = {
            'Ksiazka': Ksiazka,
            'CD': CD,
            'DVD': DVD,
            'Kaseta': Kaseta,
            'Dziennik': Dziennik,
            'Tygodnik': Tygodnik,
            'Miesiecznik': Miesiecznik,
            'GraPlanszowa': GraPlanszowa,
            'ZasobyCyfrowe': ZasobyCyfrowe
        }
        
        for p_data in dane:
            klasa = klasy_map.get(p_data.get('klasa'))
            if klasa:
                pozycja = klasa.from_dict(p_data)
                if pozycja._wypozyczona_do:
                    pozycja._wypozyczona_do = datetime.fromisoformat(pozycja._wypozyczona_do)
                if pozycja._data_rezerwacji:
                    pozycja._data_rezerwacji = datetime.fromisoformat(pozycja._data_rezerwacji)
                self.pozycje.dodaj(pozycja)
        
        return f"Wczytano {len(dane)} pozycji z pliku {nazwa_pliku}"
    
    def zapisz_czytelnikow(self, nazwa_pliku: str = "czytelnicy.json") -> str:
        dane = [czytelnik.to_dict() for czytelnik in self.czytelnicy]
        with open(nazwa_pliku, 'w', encoding='utf-8') as f:
            json.dump(dane, f, ensure_ascii=False, indent=2)
        return f"Zapisano {len(dane)} czytelników do pliku {nazwa_pliku}"
    
    def wczytaj_czytelnikow(self, nazwa_pliku: str = "czytelnicy.json") -> str:
        try:
            with open(nazwa_pliku, 'r', encoding='utf-8') as f:
                dane = json.load(f)
        except FileNotFoundError:
            return "Błąd: Plik nie istnieje"
        
        self.czytelnicy = KolekcjaCzytelnikow()
        for c_data in dane:
            c = Czytelnik.from_dict(c_data)
            self.czytelnicy.dodaj(c)
        
        return f"Wczytano {len(dane)} czytelników z pliku {nazwa_pliku}"
    
    def zapisz_pracownikow(self, nazwa_pliku: str = "pracownicy.json") -> str:
        dane = [pracownik.to_dict() for pracownik in self.pracownicy]
        with open(nazwa_pliku, 'w', encoding='utf-8') as f:
            json.dump(dane, f, ensure_ascii=False, indent=2)
        return f"Zapisano {len(dane)} pracowników do pliku {nazwa_pliku}"
    
    def wczytaj_pracownikow(self, nazwa_pliku: str = "pracownicy.json") -> str:
        try:
            with open(nazwa_pliku, 'r', encoding='utf-8') as f:
                dane = json.load(f)
        except FileNotFoundError:
            return "Błąd: Plik nie istnieje"
        
        self.pracownicy = KolekcjaPracownikow()
        for p_data in dane:
            p = Pracownik.from_dict(p_data)
            self.pracownicy.dodaj(p)
        
        return f"Wczytano {len(dane)} pracowników z pliku {nazwa_pliku}"
    
    def zapisz_wypozyczenia(self, nazwa_pliku: str = "wypozyczenia.json") -> str:
        dane = []
        for czytelnik in self.czytelnicy:
            for kod in czytelnik._wypozyczone:
                pozycja = self.pozycje.znajdz(kod)
                if pozycja and pozycja._wypozyczona_do:
                    dane.append({
                        'id_czytelnika': czytelnik.id_czytelnika,
                        'kod_biblioteczny': kod,
                        'tytul': pozycja.tytul,
                        'wypozyczona_do': pozycja._wypozyczona_do.isoformat()
                    })
        with open(nazwa_pliku, 'w', encoding='utf-8') as f:
            json.dump(dane, f, ensure_ascii=False, indent=2)
        return f"Zapisano {len(dane)} wypożyczeń do pliku {nazwa_pliku}"
    
    def wczytaj_wypozyczenia(self, nazwa_pliku: str = "wypozyczenia.json") -> str:
        try:
            with open(nazwa_pliku, 'r', encoding='utf-8') as f:
                dane = json.load(f)
        except FileNotFoundError:
            return "Błąd: Plik nie istnieje"
        
        licznik = 0
        for wp in dane:
            id_czytelnika = wp.get('id_czytelnika')
            kod = wp.get('kod_biblioteczny')
            data_str = wp.get('wypozyczona_do')
            
            czytelnik = self.czytelnicy.znajdz(id_czytelnika)
            pozycja = self.pozycje.znajdz(kod)
            
            if czytelnik and pozycja:
                pozycja._dostepna = False
                pozycja._zarezerwowana_przez = None
                pozycja._data_rezerwacji = None
                if data_str:
                    pozycja._wypozyczona_do = datetime.fromisoformat(data_str)
                else:
                    pozycja._wypozyczona_do = datetime.now() + timedelta(days=Konfiguracja.dni_wypozyczenia)
                czytelnik._wypozyczone.append(kod)
                licznik += 1
        
        return f"Wczytano {licznik} wypożyczeń z pliku {nazwa_pliku}"