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
from io import BytesIO
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],
        allowed_methods=["GET"]  # Заменено с method_whitelist на allowed_methods
    )
    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 is_service_data(entry: Dict) -> bool:
    """
    Определяет, является ли запись служебной.

    Args:
        entry (Dict): Запись данных.

    Returns:
        bool: True если служебная запись, иначе False.
    """
    if entry is None:
        return True
    if not isinstance(entry, dict):
        return True
    # Проверяем наличие служебных полей
    service_fields = {'type', 'level', 'title', 'checklist', 'short_title'}
    return service_fields.issubset(entry.keys())

def filter_borrowers_for_display(data: List[Dict]) -> List[Dict]:
    """
    Фильтрует данные, исключая служебные записи.

    Args:
        data (List[Dict]): Список всех записей заёмщиков.

    Returns:
        List[Dict]: Список записей без служебных данных.
    """
    return [entry for entry in data if not is_service_data(entry)]

def clean_value(value: Union[str, int, float]) -> str:
    """Очищает и форматирует значение для Excel."""
    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_excel(data: List[Dict]) -> Union[bytes, None]:
    """
    Сохраняет данные в формат XLSX.

    Args:
        data (List[Dict]): Список словарей с данными для сохранения.

    Returns:
        Union[bytes, None]: XLSX данные в виде байтов или None в случае ошибки.
    """
    if not data:
        logger.error("Нет данных для сохранения в XLSX.")
        return None

    output = BytesIO()

    fieldnames = sorted(set(key for item in data for key in item.keys()))

    df = pd.DataFrame(data)[fieldnames]

    try:
        df.to_excel(output, index=False, engine='openpyxl')
        xlsx_data = output.getvalue()
        output.close()
        return xlsx_data
    except Exception as e:
        logger.error(f"Ошибка сохранения в XLSX: {e}")
        return None

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.session_state.borrowers_data = data
                st.session_state.enable_save = True

                # Фильтруем данные для отображения
                filtered_data = filter_borrowers_for_display(data)
                if filtered_data:
                    st.dataframe(pd.DataFrame(filtered_data))
                else:
                    st.info("Нет данных для отображения после фильтрации.")
            else:
                st.error("Нет данных, полученных от API.")
                st.session_state.enable_save = False

        if st.button("Сохранить в XLSX", disabled=not st.session_state.get('enable_save', False)):
            if hasattr(st.session_state, 'borrowers_data'):
                xlsx_data = save_to_excel(st.session_state.borrowers_data)
                if xlsx_data:
                    st.download_button(
                        label="Скачать XLSX файл",
                        data=xlsx_data,
                        file_name="borrowers_data.xlsx",
                        mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                    )
                else:
                    st.error("Ошибка при сохранении данных в XLSX.")
            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],
                        allowed_methods=["POST"]  # Заменено с method_whitelist на allowed_methods
                    )
                    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() предложи улучш