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
from urllib.parse import urljoin

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


def read_config() -> Dict[str, str]:
    """
    Читает конфигурационный файл и возвращает настройки.
    """
    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}',
        'Content-Type': 'application/json'
    }


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.
    """
    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"]
    )
    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 формат.
    """
    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_uploaded_file(file) -> List[str]:
    """
    Обрабатывает загруженный файл (Excel или CSV) и извлекает ИНН.
    """
    try:
        file_extension = os.path.splitext(file.name)[1].lower()
        if file_extension == '.xlsx':
            df = pd.read_excel(file)
        elif file_extension == '.csv':
            df = pd.read_csv(file)
        else:
            st.error("Неподдерживаемый формат файла.")
            return []

        if df.empty:
            st.warning("Файл пуст.")
            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"Ошибка обработки файла: {str(e)}")
        logger.error(f"Ошибка обработки файла: {str(e)}")
        return []


def post_data(session: requests.Session, url: str, headers: Dict[str, str], inn: str) -> Union[bool, str]:
    """
    Отправляет данные ИНН в API и возвращает полученный id.

    Args:
        session (requests.Session): Сессия запросов.
        url (str): URL для отправки данных.
        headers (Dict[str, str]): Заголовки запроса.
        inn (str): ИНН для отправки.

    Returns:
        Union[bool, str]: Возвращает id заявки или False в случае ошибки.
    """
    data = {"inn": inn}
    try:
        response = session.post(url, headers=headers, json=data, timeout=10)
        response.raise_for_status()
        response_data = response.json()
        # Предполагаем, что API возвращает id в поле 'id'
        request_id = response_data.get('id')
        if request_id:
            return request_id
        else:
            logger.error(f"Не получил 'id' в ответе для ИНН {inn}")
            return False
    except requests.exceptions.RequestException as err:
        logger.error(f"Ошибка отправки данных для ИНН {inn}: {err}")
        return False


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

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

    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 == "Обработать данные из файла":
        st.header("Обработка данных из файла")
        uploaded_file = st.file_uploader("Выберите файл Excel или CSV", type=["xlsx", "csv"])
        if uploaded_file is not None:
            st.info("Файл загружен. Обработка...")
            data = process_uploaded_file(uploaded_file)
            if data:
                st.success(f"Файл успешно обработан. Найдено {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"]
                    )
                    adapter = HTTPAdapter(max_retries=retry_strategy)
                    session.mount('https://', adapter)
                    session.mount('http://', adapter)
                    headers = get_headers()
                    successful_count = 0
                    failed_count = 0
                    request_ids = []

                    # Формируем корректный API endpoint
                    api_endpoint = urljoin(API_URL + '/', 'borrowers/new')

                    with ThreadPoolExecutor(max_workers=10) as executor:
                        future_to_inn = {
                            executor.submit(post_data, session, api_endpoint, 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:
                                result = future.result()
                                if result:
                                    successful_count += 1
                                    request_ids.append({'inn': inn, 'request_id': result})
                                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} неудачных")

                    if request_ids:
                        st.write("Полученные request_id для ИНН:")
                        st.table(pd.DataFrame(request_ids))

                        # Возможность сохранить request_id в CSV
                        if st.button("Сохранить request_id в CSV"):
                            csv_output = StringIO()
                            df_ids = pd.DataFrame(request_ids)
                            df_ids.to_csv(csv_output, index=False)
                            csv_contents = csv_output.getvalue()
                            st.download_button(
                                label="Скачать файл с request_id",
                                data=csv_contents,
                                file_name="request_ids.csv",
                                mime="text/csv"
                            )
            else:
                st.error("Не удалось обработать файл. Пожалуйста, проверьте формат и содержимое файла.")


if __name__ == "__main__":
    if 'enable_save' not in st.session_state:
        st.session_state.enable_save = False
    if 'borrowers_data' not in st.session_state:
        st.session_state.borrowers_data = []
    main() список зависимостей напиши