переделай отправку по апи на формат csv: import streamlit as st
import pandas as pd
import requests
from urllib3.util import Retry
from requests.adapters import HTTPAdapter
import configparser
from loguru import logger
import socket
import time
import os
import json
from io import StringIO
import csv
import re
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict, Union

# Setup logger
logger.add("script.log", rotation="10 MB")

def read_config() -> Dict[str, str]:
    """
    Читает конфигурационный файл и возвращает настройки.
    
    Returns:
        Dict[str, str]: Словарь с настройками API_URL и YOUR_TOKEN.
    
    Raises:
        FileNotFoundError: Если файл конфигурации не найден.
        ValueError: Если отсутствуют обязательные настройки.
    """
    config_file = os.getenv('CONFIG_FILE', 'config.ini')
    if not os.path.exists(config_file):
        logger.error(f"Файл конфигурации {config_file} не найден.")
        raise FileNotFoundError(f"Файл конфигурации {config_file} не найден.")
    
    config = configparser.ConfigParser()
    config.read(config_file)
    
    try:
        api_url = config['default']['API_URL']
        token = config['default']['YOUR_TOKEN']
        if not api_url or not token:
            raise ValueError("API_URL или YOUR_TOKEN не указаны в файле конфигурации")
        return {'API_URL': api_url, 'YOUR_TOKEN': token}
    except KeyError as e:
        logger.error(f"Ошибка конфигурации: {e}")
        raise ValueError(f"Ошибка конфигурации: {e}")

# Load configuration
try:
    config = read_config()
    API_URL = config['API_URL']
    YOUR_TOKEN = config['YOUR_TOKEN']
    logger.info(f"API_URL: {API_URL}")
    logger.info(f"YOUR_TOKEN: {'***' if YOUR_TOKEN else 'Не установлен'}")
except (FileNotFoundError, ValueError) as e:
    st.error(str(e))
    st.stop()

def get_headers() -> Dict[str, str]:
    """Возвращает заголовки для API запросов."""
    return {'Authorization': f'Token {YOUR_TOKEN}'}

def check_internet(host="8.8.8.8", port=53, timeout=3) -> bool:
    """Проверяет доступность интернет-соединения."""
    try:
        socket.setdefaulttimeout(timeout)
        socket.create_connection((host, port))
        logger.info("Интернет-соединение доступно.")
        return True
    except OSError as e:
        logger.error(f"Нет интернет-соединения: {e}")
        return False

@st.cache_data(show_spinner=False)
def get_all_borrowers(limit: int = 250, delay: int = 5, max_retries: int = 3) -> List[Dict]:
    """
    Получает данные всех заемщиков через API.
    
    Args:
        limit (int): Лимит записей на запрос.
        delay (int): Задержка между запросами.
        max_retries (int): Максимальное количество попыток при ошибке.
    
    Returns:
        List[Dict]: Список словарей с данными заемщиков.
    """
    all_data = []
    offset = 0
    progress_bar = st.progress(0)
    status_text = st.empty()

    session = requests.Session()
    retry_strategy = Retry(
        total=max_retries,
        backoff_factor=1,
        status_forcelist=[500, 502, 503, 504],
        method_whitelist=["GET"]
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount('https://', adapter)
    session.mount('http://', adapter)

    while True:
        try:
            url = f"{API_URL}/account/deals/borrowers?limit={limit}&offset={offset}"
            logger.info(f"Запрос к URL: {url}")
            response = session.get(url, headers=get_headers(), timeout=30)
            response.raise_for_status()
            data = response.json()
            
            if isinstance(data, dict):
                data = data.get('results') or data.get('data', [])
            
            if not data:
                break

            all_data.extend(data if isinstance(data, list) else [data])
            
            logger.info(f"Получено {len(data)} записей.")
            status_text.text(f"Получено {len(all_data)} записей.")
            progress = len(all_data) / 1000  # Примерная оценка
            progress_bar.progress(min(progress, 1.0))
            
            if len(data) < limit:
                break
            
            offset += limit
            time.sleep(delay)

        except requests.exceptions.RequestException as e:
            logger.error(f"Ошибка запроса API: {e}")
            time.sleep(delay * 2)

    logger.info(f"Всего получено записей: {len(all_data)}")
    status_text.text(f"Всего получено записей: {len(all_data)}")
    return all_data

def clean_value(value: Union[str, int, float]) -> str:
    """Очищает и форматирует значение для CSV."""
    if isinstance(value, str):
        value = value.strip('"')
        value = re.sub(r'[\[\]{}\'"]', '', value)
        value = re.sub(r'\s+', ' ', value)
        return value.strip()
    return str(value)

def save_to_csv(data: List[Dict]) -> Union[str, None]:
    """
    Сохраняет данные в CSV формат.
    
    Args:
        data (List[Dict]): Список словарей с данными для сохранения.
    
    Returns:
        Union[str, None]: CSV данные в виде строки или None в случае ошибки.
    """
    if not data:
        logger.error("Нет данных для сохранения в CSV.")
        return None
    
    output = StringIO()
    writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
    exclude_fields = {'type', 'level', 'title', 'checklist'}
    
    fieldnames = sorted(set(key for item in data for key in item.keys() if key not in exclude_fields))
    writer.writerow(fieldnames)
    
    for item in data:
        row = [clean_value(item.get(field, '')) for field in fieldnames]
        writer.writerow(row)
    
    output.seek(0)
    return output.getvalue()

def process_excel_data(file) -> List[str]:
    """
    Обрабатывает Excel файл и извлекает ИНН.
    
    Args:
        file: Загруженный файл Excel.
    
    Returns:
        List[str]: Список найденных ИНН.
    """
    try:
        df = pd.read_excel(file)
        if df.empty:
            st.warning("Excel файл пуст.")
            return []
        
        inn_column = next((col for col in df.columns if 'inn' in str(col).lower() or 'инн' in str(col).lower()), None)
        
        if inn_column is None:
            inn_data = df.iloc[:, 0].astype(str).apply(lambda x: re.findall(r'\d{10,12}', x))
        else:
            inn_data = df[inn_column].astype(str).apply(lambda x: re.findall(r'\d{10,12}', x))
        
        inn_data = inn_data[inn_data.apply(bool)].apply(lambda x: x[0])
        
        if inn_data.empty:
            st.error("Не удалось найти корректные данные ИНН.")
            return []

        return inn_data.dropna().tolist()
    except Exception as e:
        st.error(f"Ошибка обработки Excel-файла: {str(e)}")
        logger.error(f"Ошибка обработки Excel-файла: {str(e)}")
        return []

def post_data(session: requests.Session, url: str, headers: Dict[str, str], inn: str) -> bool:
    """
    Отправляет данные ИНН в API.
    
    Args:
        session (requests.Session): Сессия запросов.
        url (str): URL для отправки данных.
        headers (Dict[str, str]): Заголовки запроса.
        inn (str): ИНН для отправки.
    
    Returns:
        bool: True если отправка успешна, иначе False.
    """
    data = {"inn": inn}
    try:
        response = session.post(url, headers=headers, json=data, timeout=10)
        response.raise_for_status()
        return True
    except requests.exceptions.RequestException as err:
        logger.error(f"Ошибка отправки данных для ИНН {inn}: {err}")
        return False

def main():
    st.title("Панель администрирования обработки данных")

    page = st.sidebar.selectbox("Выберите задачу", ["Получить данные заемщиков", "Обработать Excel-данные"])

    if page == "Получить данные заемщиков":
        st.header("Получить данные заемщиков")
        if st.button("Проверить интернет-соединение"):
            if check_internet():
                st.success("Интернет-соединение доступно.")
            else:
                st.error("Нет интернет-соединения.")

        if st.button("Получить данные заемщиков"):
            with st.spinner("Получение данных..."):
                data = get_all_borrowers()
            if data:
                st.success(f"Получено {len(data)} записей.")
                st.dataframe(pd.DataFrame(data))
                st.session_state.borrowers_data = data
                st.session_state.enable_save = True
            else:
                st.error("Нет данных, полученных от API.")
                st.session_state.enable_save = False

        if st.button("Сохранить в CSV", disabled=not st.session_state.get('enable_save', False)):
            if hasattr(st.session_state, 'borrowers_data'):
                csv_data = save_to_csv(st.session_state.borrowers_data)
                if csv_data:
                    st.download_button(
                        label="Скачать CSV файл",
                        data=csv_data,
                        file_name="borrowers_data.csv",
                        mime="text/csv"
                    )
            else:
                st.error("Нет данных для сохранения. Пожалуйста, сначала получите данные.")

    elif page == "Обработать Excel-данные":
        st.header("Обработка Excel-данных")
        uploaded_file = st.file_uploader("Выберите Excel-файл", type="xlsx")
        if uploaded_file is not None:
            st.info("Файл загружен. Обработка...")
            data = process_excel_data(uploaded_file)
            if data:
                st.success(f"Excel-файл успешно обработан. Найдено {len(data)} ИНН.")
                st.write(data[:10])  # Показываем первые 10 ИНН

                if st.button("Отправить данные в API"):
                    progress_bar = st.progress(0)
                    status_text = st.empty()
                    session = requests.Session()
                    retry_strategy = Retry(
                        total=5,
                        backoff_factor=0.1,
                        status_forcelist=[500, 502, 503, 504],
                        method_whitelist=["POST"]
                    )
                    adapter = HTTPAdapter(max_retries=retry_strategy)
                    session.mount('https://', adapter)
                    session.mount('http://', adapter)
                    headers = get_headers()
                    successful_count = 0
                    failed_count = 0

                    with ThreadPoolExecutor(max_workers=10) as executor:
                        future_to_inn = {
                            executor.submit(post_data, session, f"{API_URL}/account/deals/borrowers", headers, inn): inn
                            for inn in data
                        }
                        total = len(future_to_inn)
                        for i, future in enumerate(as_completed(future_to_inn), 1):
                            inn = future_to_inn[future]
                            try:
                                if future.result():
                                    successful_count += 1
                                else:
                                    failed_count += 1
                            except Exception as exc:
                                logger.error(f"Исключение для ИНН {inn}: {exc}")
                                failed_count += 1
                            
                            progress_bar.progress(i / total)
                            status_text.text(f"Обработано {i}/{total} ИНН")

                    st.success(f"Отправка данных завершена: {successful_count} успешных, {failed_count} неудачных")
            else:
                st.error("Не удалось обработать Excel-файл. Пожалуйста, проверьте формат и содержимое файла.")

if __name__ == "__main__":
    if 'enable_save' not in st.session_state:
        st.session_state.enable_save = False
    main()