import requests
import os
from datetime import datetime, timedelta
import time
import json
import re
import logging
from config import Config
Config.ensure_dirs()

# Konfiguracja logowania
def setup_logging():
    """Konfiguruje system logowania"""
    log_file = os.path.join(Config.LOGS_DIR, 'sec_downloader.log')
    
    # Konfiguracja handlera dla pliku
    file_handler = logging.FileHandler(log_file, encoding='utf-8')
    file_handler.setLevel(logging.DEBUG)
    file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    file_handler.setFormatter(file_formatter)
    
    # Konfiguracja handlera dla konsoli
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_formatter = logging.Formatter('%(levelname)s: %(message)s')
    console_handler.setFormatter(console_formatter)
    
    # Konfiguracja głównego loggera
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger

# Usuń poprzednią konfigurację logging.basicConfig i zastąp ją wywołaniem setup_logging
logger = setup_logging()

# Dodajemy stałe na początku pliku, zaraz po importach
MAX_8K_REPORTS = 4
MAX_PROXY_REPORTS = 1
MAX_REPORT_AGE_YEARS = 4
MAX_FORM4_REPORTS = 50  # Ostatnie transakcje insiderów
MAX_SC13G_REPORTS = 10  # Ostatnie zmiany znaczących udziałowców
MAX_SECURITIES_REPORTS = 5  # Ostatnie emisje papierów wartościowych

# Okresy dla różnych typów raportów (w miesiącach)
REPORT_PERIODS = {
    '4': 12,      # Formularze 4
    'SC 13G': 12, # Zmiany udziałów
    'S-3': 6,     # Rejestracje papierów
    '424B5': 6    # Prospekty emisyjne
}

# Dodaj słownik obsługiwanych typów raportów i ich limitów
SUPPORTED_REPORTS = {
    # Podstawowe raporty finansowe
    '10-K': {'max_count': 2, 'period_months': 24},
    '10-Q': {'max_count': 4, 'period_months': 12},
    '8-K': {'max_count': MAX_8K_REPORTS, 'period_months': 12},
    'DEF 14A': {'max_count': MAX_PROXY_REPORTS, 'period_months': 12},
    
    # Raporty insiderów i znaczących udziałowców
    '4': {'max_count': MAX_FORM4_REPORTS, 'period_months': 12},
    'SC 13G': {'max_count': MAX_SC13G_REPORTS, 'period_months': 12},
    'SC 13G/A': {'max_count': MAX_SC13G_REPORTS, 'period_months': 12},
    
    # Raporty o papierach wartościowych
    'S-3': {'max_count': MAX_SECURITIES_REPORTS, 'period_months': 6},
    'S-3ASR': {'max_count': MAX_SECURITIES_REPORTS, 'period_months': 6},
    '424B5': {'max_count': MAX_SECURITIES_REPORTS, 'period_months': 6},
}

def get_cik_from_ticker(ticker):
    if not ticker or not isinstance(ticker, str):
        print("Błędny format tickera")
        return None
    if len(ticker) > 5:  # Większość tickerów ma max 5 znaków
        print("Ticker jest zbyt długi")
        return None
    
    print(f"\n=== Wyszukiwanie CIK dla tickera {ticker} ===")
    
    # URL do wyszukiwania CIK
    search_url = "https://www.sec.gov/include/ticker.txt"
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept': 'text/plain',
        'Host': 'www.sec.gov'
    }
    
    try:
        response = requests.get(search_url, headers=headers)
        if response.status_code == 200:
            # Plik zawiera dane w formacie: ticker\tCIK
            ticker_data = response.text.split('\n')
            for line in ticker_data:
                if line.strip():
                    t, cik = line.strip().split('\t')
                    if t.lower() == ticker.lower():
                        return cik.zfill(10)
            print(f"Nie znaleziono CIK dla tickera {ticker}")
            return None
        else:
            print(f"Błąd podczas wyszukiwania CIK. Status: {response.status_code}")
            return None
    except Exception as e:
        print(f"Błąd podczas wyszukiwania CIK: {str(e)}")
        return None

def download_with_retry(url, headers, max_retries=3, delay=1):
    """
    Pobiera dane z URL z obsługą ponownych prób i limitów API.
    
    Args:
        url (str): URL do pobrania
        headers (dict): Nagłówki HTTP
        max_retries (int): Maksymalna liczba prób
        delay (int): Podstawowe opóźnienie między próbami (w sekundach)
    
    Returns:
        Response: Obiekt odpowiedzi requests lub None w przypadku błędu
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url, headers=headers)
            if response.status_code == 429:  # Rate limit
                wait_time = (attempt + 1) * delay * 2
                logging.warning(f"Rate limit przekroczony. Czekam {wait_time} sekund...")
                time.sleep(wait_time)
                continue
            elif response.status_code == 200:
                return response
            else:
                logging.error(f"Błąd HTTP {response.status_code} dla URL: {url}")
                time.sleep(delay)
        except Exception as e:
            logging.error(f"Próba {attempt + 1}/{max_retries} nie powiodła się: {str(e)}")
            if attempt == max_retries - 1:
                raise e
            time.sleep(delay)
    return None

def get_file_path(save_dir, date, form, accession):
    """
    Generuje standardową ścieżkę pliku dla raportu.
    
    Args:
        save_dir (str): Katalog docelowy
        date (str): Data raportu
        form (str): Typ formularza
        accession (str): Numer akcesyjny
    
    Returns:
        str: Pełna ścieżka do pliku
    """
    file_name = f"{date}_{form}_{accession}.txt"
    return os.path.join(save_dir, file_name)

def check_if_report_exists(save_dir, date, form, accession):
    """
    Sprawdza czy raport istnieje w katalogu.
    
    Args:
        save_dir (str): Katalog do sprawdzenia
        date (str): Data raportu
        form (str): Typ formularza
        accession (str): Numer akcesyjny
    
    Returns:
        bool: True jeśli plik istnieje, False w przeciwnym razie
    """
    file_path = get_file_path(save_dir, date, form, accession)
    exists = os.path.isfile(file_path)
    if exists:
        logging.info(f"Znaleziono istniejący plik: {file_path}")
    return exists

def get_latest_report_date(save_dir):
    """
    Znajduje datę najnowszego pobranego raportu.
    
    Args:
        save_dir (str): Katalog z raportami
    
    Returns:
        str: Data najnowszego raportu w formacie YYYY-MM-DD lub None jeśli brak raportów
    """
    if not os.path.exists(save_dir):
        return None
        
    latest_date = None
    for filename in os.listdir(save_dir):
        if not filename.endswith('.txt'):
            continue
        try:
            file_date = filename.split('_')[0]  # Format: YYYY-MM-DD_...
            if latest_date is None or file_date > latest_date:
                latest_date = file_date
        except:
            continue
            
    return latest_date

def download_financial_reports(cik):
    """
    Pobiera raporty finansowe dla podanego CIK.
    """
    logger.info(f"=== Rozpoczynam wyszukiwanie raportów dla {cik} ===")
    
    # Konfiguracja podstawowa
    save_dir = Config.get_company_dir(cik)
    os.makedirs(save_dir, exist_ok=True)
    
    # Sprawdź datę najnowszego lokalnego raportu
    latest_local_date = get_latest_report_date(save_dir)
    if latest_local_date:
        logger.info(f"Najnowszy lokalny raport z daty: {latest_local_date}")
    
    padded_cik = str(cik).zfill(10)
    
    headers = {
        'User-Agent': 'Individual kwaq@example.com',
        'Accept': 'application/json',
        'Accept-Encoding': 'gzip, deflate',
        'Host': 'data.sec.gov'
    }
    
    submissions_url = f"https://data.sec.gov/submissions/CIK{padded_cik}.json"
    
    try:
        # Pobieranie listy dokumentów z obsługą ponownych prób
        response = download_with_retry(submissions_url, headers)
        if not response:
            logger.error("Nie udało się pobrać listy dokumentów")
            return
            
        data = response.json()
        recent_filings = data.get('filings', {}).get('recent', {})
        
        # Liczniki raportów
        stats = {
            'downloaded': 0,
            'skipped': 0,
            'errors': 0,
            'form_8k_count': 0,
            'proxy_count': 0
        }
        
        # Iteracja przez dokumenty
        for idx, (form, date, accession) in enumerate(zip(
            recent_filings.get('form', []),
            recent_filings.get('filingDate', []),
            recent_filings.get('accessionNumber', [])
        )):
            # Sprawdzamy czy raport powinien być pobrany
            if not should_download_report(form, date, stats):
                stats['skipped'] += 1
                continue
                
            # Pobieramy tylko jeśli jest nowszy niż lokalny
            if latest_local_date and date <= latest_local_date:
                logger.debug(f"Pomijam starszy raport {form} z {date}")
                stats['skipped'] += 1
                continue
                
            # Pobieranie dokumentu
            success = download_single_report(
                cik=padded_cik,
                form=form,
                date=date,
                accession=accession,
                save_dir=save_dir,
                headers=headers
            )
            
            if success:
                stats['downloaded'] += 1
                update_form_stats(form, stats)
            else:
                stats['errors'] += 1
            
            time.sleep(0.5)
            
        log_download_summary(stats)
        
    except Exception as e:
        logger.error(f"Błąd podczas pobierania danych: {str(e)}")

def should_download_report(form, date, stats):
    """
    Sprawdza czy należy pobrać raport danego typu.
    
    Args:
        form (str): Typ formularza
        date (str): Data raportu w formacie YYYY-MM-DD
        stats (dict): Statystyki pobrań
    """
    # Sprawdzamy czy raport nie jest starszy niż 4 lata
    try:
        report_date = datetime.strptime(date, '%Y-%m-%d')
        cutoff_date = datetime.now() - timedelta(days=MAX_REPORT_AGE_YEARS*365)
        
        if report_date < cutoff_date:
            logger.debug(f"Pomijam raport {form} z {date} - starszy niż {MAX_REPORT_AGE_YEARS} lata")
            return False
    except Exception as e:
        logger.error(f"Błąd podczas sprawdzania daty: {str(e)}")
        return False
    
    # Sprawdzamy limity dla poszczególnych typów raportów
    if form == '8-K' and stats['form_8k_count'] >= MAX_8K_REPORTS:
        logger.debug(f"Pomijam raport {form} - osiągnięto limit {MAX_8K_REPORTS}")
        return False
    if form == 'DEF 14A' and stats['proxy_count'] >= MAX_PROXY_REPORTS:
        logger.debug(f"Pomijam raport {form} - osiągnięto limit {MAX_PROXY_REPORTS}")
        return False
    if form not in ['10-K', '10-Q', '8-K', 'DEF 14A']:
        logger.debug(f"Pomijam raport {form} - nieobsługiwany typ")
        return False
    
    return True

def download_single_report(cik, form, date, accession, save_dir, headers):
    """
    Pobiera pojedynczy raport i zapisuje go do pliku.
    """
    try:
        accession_no = accession.replace('-', '')
        doc_url = f"https://www.sec.gov/Archives/edgar/data/{int(cik)}/{accession_no}/{accession}.txt"
        
        doc_headers = {
            'User-Agent': headers['User-Agent'],
            'Accept': 'text/html,application/xhtml+xml,application/xml',
            'Host': 'www.sec.gov'
        }
        
        response = download_with_retry(doc_url, doc_headers)
        if not response:
            return False
            
        file_path = get_file_path(save_dir, date, form, accession)
        with open(file_path, 'wb') as f:
            f.write(response.content)
            
        logger.info(f"Pobrano i zapisano: {file_path}")
        return True
        
    except Exception as e:
        logger.error(f"Błąd podczas pobierania dokumentu: {str(e)}")
        return False

def update_form_stats(form, stats):
    """
    Aktualizuje statystyki pobranych formularzy.
    """
    if form == '8-K':
        stats['form_8k_count'] += 1
    elif form == 'DEF 14A':
        stats['proxy_count'] += 1

def log_download_summary(stats):
    """
    Loguje podsumowanie pobranych raportów.
    """
    logger.info("\n=== Podsumowanie pobierania ===")
    logger.info(f"Pobrano nowych raportów: {stats['downloaded']}")
    logger.info(f"Pominięto istniejących: {stats['skipped']}")
    logger.info(f"Błędy pobierania: {stats['errors']}")
    logger.info(f"Pobrano raportów 8-K: {stats['form_8k_count']}")
    logger.info(f"Pobrano proxy statements: {stats['proxy_count']}")

def clean_sec_file(file_content):
    """
    Czyści plik SEC zachowując istotne dane i strukturę dokumentu.
    
    Args:
        file_content (bytes): Zawartość pliku w formacie bajtowym
    
    Returns:
        str: Oczyszczona zawartość dokumentu
    """
    try:
        # Konwertuj bajty na tekst
        content = file_content.decode('utf-8', errors='ignore')
        
        # Usuń sekcję nagłówka SEC, ale zachowaj metadane
        if '<SEC-HEADER>' in content and '</SEC-HEADER>' in content:
            header = content.split('</SEC-HEADER>')[0]
            content = content.split('</SEC-HEADER>')[1]
            
            # Wyciągnij ważne metadane z nagłówka
            metadata = []
            for line in header.splitlines():
                if any(key in line for key in ['COMPANY CONFORMED NAME:', 'FILED AS OF DATE:', 'TYPE:', 'FISCAL YEAR END:']):
                    metadata.append(line.strip())
            
            # Dodaj metadane na początku dokumentu
            content = '\n'.join(metadata) + '\n\n' + content
        
        # Zachowaj tabelaryczne dane, usuwając tylko znaczniki HTML
        content = re.sub(r'<TABLE.*?>', '\n[TABELA]\n', content, flags=re.IGNORECASE | re.DOTALL)
        content = re.sub(r'</TABLE>', '\n[KONIEC TABELI]\n', content, flags=re.IGNORECASE)
        
        # Zachowaj strukturę dokumentu
        content = re.sub(r'<(CAPTION|THEAD|TBODY|TR).*?>', '\n', content, flags=re.IGNORECASE)
        content = re.sub(r'</(CAPTION|THEAD|TBODY|TR)>', '\n', content, flags=re.IGNORECASE)
        
        # Zachowaj dane z komórek tabeli
        content = re.sub(r'<T[HD].*?>(.*?)</T[HD]>', r'\1\t', content, flags=re.IGNORECASE | re.DOTALL)
        
        # Usuń pozostałe znaczniki HTML zachowując formatowanie tekstu
        content = re.sub(r'<p.*?>', '\n\n', content, flags=re.IGNORECASE)
        content = re.sub(r'<br.*?>', '\n', content, flags=re.IGNORECASE)
        content = re.sub(r'<div.*?>', '\n', content, flags=re.IGNORECASE)
        content = re.sub(r'</div>', '\n', content, flags=re.IGNORECASE)
        
        # Usuń pozostałe znaczniki HTML, ale zachowaj tekst
        content = re.sub(r'<[^>]+>', ' ', content)
        
        # Usuń znaczniki XBRL zachowując dane
        if '<XBRL>' in content and '</XBRL>' in content:
            xbrl_data = content[content.find('<XBRL>'):content.find('</XBRL>')]
            # Wyciągnij wartości z XBRL
            xbrl_values = re.findall(r'<ix:nonNumeric[^>]*>(.*?)</ix:nonNumeric>', xbrl_data)
            xbrl_values.extend(re.findall(r'<ix:nonFraction[^>]*>(.*?)</ix:nonFraction>', xbrl_data))
            
            # Usuń sekcję XBRL ale zachowaj wyciągnięte wartości
            content = content.split('<XBRL>')[0] + '\n\n[DANE XBRL]\n' + '\n'.join(xbrl_values) + '\n[KONIEC DANYCH XBRL]\n' + content.split('</XBRL>')[1]
        
        # Zachowaj numerację sekcji i elementów
        content = re.sub(r'Item\s+(\d+[A-Za-z]?\.)', r'\n\nITEM \1', content)
        
        # Usuń wielokrotne puste linie, ale zachowaj podstawowe formatowanie
        content = re.sub(r'\n{3,}', '\n\n', content)
        
        # Usuń nadmiarowe spacje, ale zachowaj wcięcia tabel
        content = '\n'.join(line.rstrip() for line in content.splitlines())
        
        # Dodaj znaczniki sekcji dla łatwiejszej nawigacji
        sections = ['Business', 'Risk Factors', 'Management Discussion', 'Financial Statements']
        for section in sections:
            pattern = f"Item.*{section}"
            content = re.sub(pattern, f"\n\n=== {section.upper()} ===\n\n", content, flags=re.IGNORECASE)
        
        return content
        
    except Exception as e:
        logger.error(f"Błąd podczas czyszczenia pliku: {str(e)}")
        return None

def save_report(content, file_path):
    """Zapisuje oczyszczoną treść raportu."""
    try:
        # Zmień rozszerzenie na .txt dla czystego tekstu
        clean_file_path = file_path.replace('.htm', '.txt')
        
        with open(clean_file_path, 'w', encoding='utf-8') as f:
            f.write(content)
            
        logger.info(f"✓ Zapisano oczyszczony raport: {clean_file_path}")
        return True
        
    except Exception as e:
        logger.error(f"✗ Błąd podczas zapisu oczyszczonego pliku: {str(e)}")
        return False

def preprocess_report(file_path):
    """Przygotowuje tekst raportu do analizy przez AI."""
    with open(file_path, 'r', encoding='utf-8') as f:
        text = f.read()
    
    # Usuwanie pustych linii
    text = '\n'.join(line for line in text.split('\n') if line.strip())
    
    # Usuwanie wielokrotnych spacji
    text = ' '.join(text.split())
    
    # Usuwanie specjalnych znaków SEC
    text = re.sub(r'\[.*?\]', '', text)
    text = re.sub(r'\(.*?\)', '', text)
    
    return text

def clean_filename(filename):
    return re.sub(r'[<>:"/\\|?*]', '_', filename)

def extract_sections(content):
    """Wyodrębnia główne sekcje raportu."""
    sections = {
        'Management Discussion': '',
        'Financial Statements': '',
        'Risk Factors': '',
        'Business': ''
    }
    
    # Szukaj typowych nagłówków sekcji
    for line in content.splitlines():
        if 'Item 7. Management' in line:
            sections['Management Discussion'] = 'Active'
        elif 'Item 8. Financial' in line:
            sections['Financial Statements'] = 'Active'
        elif 'Item 1A. Risk Factors' in line:
            sections['Risk Factors'] = 'Active'
        elif 'Item 1. Business' in line:
            sections['Business'] = 'Active'
            
    return sections

def is_report_in_period(report_date, report_type):
    """Sprawdza czy raport mieści się w zdefiniowanym okresie"""
    if not is_report_supported(report_type):
        return False
        
    months = SUPPORTED_REPORTS[report_type]['period_months']
    cutoff_date = datetime.now() - timedelta(days=months*30)
    return report_date >= cutoff_date

def get_report_count_limit(report_type):
    """Zwraca limit ilości raportów danego typu"""
    if not is_report_supported(report_type):
        return 0
    return SUPPORTED_REPORTS[report_type]['max_count']

def get_report_directory(cik, report_type, report_date):
    """Tworzy strukturę katalogów dla raportów"""
    base_dir = f"./reports/{cik}"
    year = report_date.year
    report_category = get_report_category(report_type)
    return os.path.join(base_dir, str(year), report_category)

def get_report_category(report_type):
    """Kategoryzuje raporty"""
    categories = {
        '10-K': 'annual',
        '10-Q': 'quarterly',
        '8-K': 'current',
        'DEF 14A': 'proxy',
        '4': 'insider',
        'SC 13G': 'ownership',
        'SC 13G/A': 'ownership',
        'S-3': 'securities',
        'S-3ASR': 'securities',
        '424B5': 'securities'
    }
    return categories.get(report_type, 'other')

def is_report_supported(report_type):
    """Sprawdza czy dany typ raportu jest obsługiwany"""
    return report_type in SUPPORTED_REPORTS

def log_download_statistics(report_counts):
    """Loguje statystyki pobranych raportów"""
    logger.info("\n=== Podsumowanie pobierania ===")
    for report_type, count in report_counts.items():
        if count > 0:
            logger.info(f"Pobrano raportów {report_type}: {count}")

if __name__ == "__main__":
    try:
        logger.info("=== SEC EDGAR Report Downloader ===")
        print("\nMożesz podać ticker (np. AAPL) lub CIK (np. 0000320193)")
        user_input = input("\nPodaj ticker lub CIK: ").strip()
        
        # Sprawdzamy, czy input wygląda jak CIK (same cyfry)
        if user_input.isdigit():
            ticker = user_input
        else:
            # Jeśli to nie CIK, traktujemy jako ticker i szukamy CIK
            cik = get_cik_from_ticker(user_input)
            if cik:
                logger.info(f"Znaleziono CIK: {cik}")
                ticker = cik
            else:
                logger.error("Nie można kontynuować bez prawidłowego CIK")
                exit(1)
        
        download_financial_reports(ticker)
        logger.info("Zakończono pobieranie raportów")
    except Exception as e:
        logger.error(f"Wystąpił błąd: {str(e)}", exc_info=True)
        exit(1)

# Przykład użycia:
# Skrypt można uruchomić, a następnie podać CIK np. "0000320193", aby pobrać raporty dla Apple.
# Raporty będą zapisane w folderze "./reports/<ticker>/<rok>".