import json
import os
import string
import argparse
import logging
from collections import defaultdict, Counter
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from html import escape

# Настройка логирования
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.StreamHandler()
    ]
)

def load_json(file_path: str) -> Optional[Dict[str, Any]]:
    """
    Загружает JSON файл.

    :param file_path: Путь к JSON файлу.
    :return: Загруженные данные или None при ошибке.
    """
    if not os.path.exists(file_path):
        logging.error(f"Входной файл '{file_path}' не найден.")
        return None
    try:
        with open(file_path, 'r', encoding='utf-8') as infile:
            data = json.load(infile)
            logging.info(f"Файл '{file_path}' успешно загружен.")
            return data
    except json.JSONDecodeError as e:
        logging.error(f"Ошибка декодирования JSON: {e}")
        return None
    except Exception as e:
        logging.error(f"Неожиданная ошибка при загрузке файла: {e}")
        return None

def save_json(data: Any, file_path: str) -> None:
    """
    Сохраняет данные в JSON файл.

    :param data: Данные для сохранения.
    :param file_path: Путь к файлу для сохранения.
    """
    try:
        with open(file_path, 'w', encoding='utf-8') as outfile:
            json.dump(data, outfile, ensure_ascii=False, indent=2)
        logging.info(f"Данные успешно сохранены в '{file_path}'.")
    except Exception as e:
        logging.error(f"Ошибка при сохранении файла: {e}")

def save_html(content: str, file_path: str) -> None:
    """
    Сохраняет HTML контент в файл.

    :param content: HTML контент.
    :param file_path: Путь к файлу для сохранения.
    """
    try:
        with open(file_path, 'w', encoding='utf-8') as html_file:
            html_file.write(content)
        logging.info(f"HTML отчет успешно сохранен в '{file_path}'.")
    except Exception as e:
        logging.error(f"Ошибка при сохранении HTML файла: {e}")

def extract_text(text_content: Any) -> str:
    """
    Извлекает текст из различных форматов содержимого.

    :param text_content: Содержимое сообщения.
    :return: Извлечённый текст.
    """
    if isinstance(text_content, str):
        return text_content
    elif isinstance(text_content, list):
        text_parts = []
        for item in text_content:
            if isinstance(item, str):
                text_parts.append(item)
            elif isinstance(item, dict) and 'text' in item:
                text_parts.append(item['text'])
        return ''.join(text_parts)
    elif isinstance(text_content, dict) and 'text' in text_content:
        return text_content['text']
    else:
        return ''

def tokenize_text(text: str) -> List[str]:
    """
    Токенизирует текст: приводит к нижнему регистру и удаляет пунктуацию.

    :param text: Исходный текст.
    :return: Список слов.
    """
    text = text.lower()
    translator = str.maketrans('', '', string.punctuation)
    text = text.translate(translator)
    words = text.split()
    return words

def weekday_order(weekday: str) -> int:
    """
    Возвращает порядковый номер дня недели.

    :param weekday: Название дня недели на русском.
    :return: Порядковый номер.
    """
    order = {
        'Понедельник': 1, 'Вторник': 2, 'Среда': 3, 'Четверг': 4,
        'Пятница': 5, 'Суббота': 6, 'Воскресенье': 7
    }
    return order.get(weekday, 8)

def month_order(month: str) -> int:
    """
    Возвращает порядковый номер месяца.

    :param month: Название месяца на русском.
    :return: Порядковый номер.
    """
    order = {
        'Январь': 1, 'Февраль': 2, 'Март': 3, 'Апрель': 4,
        'Май': 5, 'Июнь': 6, 'Июль': 7, 'Август': 8,
        'Сентябрь': 9, 'Октябрь': 10, 'Ноябрь': 11, 'Декабрь': 12
    }
    return order.get(month, 13)

def get_time_of_day(hour: int) -> str:
    """
    Классифицирует время суток по часу.

    :param hour: Час суток.
    :return: Время суток.
    """
    if 6 <= hour < 12:
        return 'Утро'
    elif 12 <= hour < 18:
        return 'День'
    elif 18 <= hour < 24:
        return 'Вечер'
    else:
        return 'Ночь'

def analyze_data(data: Dict[str, Any]) -> Dict[str, Any]:
    """
    Анализирует данные чата.

    :param data: Загруженные данные чата.
    :return: Словарь с результатами анализа.
    """
    cleaned_messages = []
    user_message_counts: Dict[str, int] = defaultdict(int)
    user_message_lengths: Dict[str, List[int]] = defaultdict(list)
    activity_by_hour: Dict[int, int] = defaultdict(int)
    activity_by_weekday: Dict[str, int] = defaultdict(int)
    activity_by_month: Dict[str, int] = defaultdict(int)
    activity_by_time_of_day: Dict[str, int] = defaultdict(int)
    word_counter: Counter = Counter()
    user_interactions: Dict[str, Dict[str, int]] = defaultdict(lambda: defaultdict(int))
    response_times: Dict[str, List[float]] = defaultdict(list)

    # Набор русских стоп-слов
    russian_stopwords = {
        'и', 'в', 'во', 'не', 'что', 'он', 'на', 'я', 'с', 'со',
        'как', 'а', 'то', 'все', 'она', 'так', 'его', 'но', 'да',
        'ты', 'к', 'у', 'же', 'вы', 'за', 'бы', 'по', 'только', 'ее',
        'мне', 'было', 'вот', 'от', 'меня', 'еще', 'нет', 'о', 'из',
        'ему', 'теперь', 'когда', 'даже', 'ну', 'вдруг', 'ли', 'если',
        'уже', 'или', 'ни', 'быть', 'был', 'него', 'до', 'вас', 'нибудь',
        'опять', 'уж', 'вам', 'ведь', 'там', 'потом', 'себя', 'ничего',
        'ей', 'может', 'они', 'тут', 'где', 'есть', 'надо', 'ней', 'для',
        'мы', 'тебя', 'их', 'чем', 'была', 'сам', 'чтобы', 'без', 'будто',
        'чего', 'раз', 'тоже', 'себе', 'под', 'будет', 'ж', 'тогда', 'кто',
        'этот', 'того', 'потому', 'этого', 'какой', 'совсем', 'ним', 'здесь',
        'этом', 'один', 'почти', 'мой', 'тем', 'чтобы', 'нее', 'кажется', 'сюда',
        'туда', 'этой'
    }

    users = set()

    messages = data.get('messages', [])
    total_messages = len(messages)
    logging.info(f"Начало анализа {total_messages} сообщений.")

    # Создание словаря для быстрого поиска сообщений по id
    message_id_map = {msg['id']: msg for msg in messages if 'id' in msg}

    for message in messages:
        user = message.get('from', 'Неизвестный пользователь')
        if user != 'Неизвестный пользователь':
            users.add(user)

        user_message_counts[user] += 1

        cleaned_text = extract_text(message.get('text', ''))
        cleaned_message = {
            'id': message.get('id'),
            'date': message.get('date'),
            'from': user,
            'text': cleaned_text
        }
        cleaned_messages.append(cleaned_message)

        message_length = len(cleaned_text)
        user_message_lengths[user].append(message_length)

        message_date = message.get('date')
        if message_date:
            try:
                dt = datetime.fromisoformat(message_date)
                hour = dt.hour
                weekday = dt.strftime('%A')
                month = dt.strftime('%B')
                time_of_day = get_time_of_day(hour)
                activity_by_hour[hour] += 1
                activity_by_weekday[weekday] += 1
                activity_by_month[month] += 1
                activity_by_time_of_day[time_of_day] += 1
            except ValueError:
                logging.warning(f"Некорректный формат даты: {message_date}")

        words = tokenize_text(cleaned_text)
        filtered_words = [word for word in words if word not in russian_stopwords]
        word_counter.update(filtered_words)

        # Анализ взаимодействий между пользователями
        reply_message_id = message.get('reply_to_message_id')
        if reply_message_id:
            reply_message = message_id_map.get(reply_message_id)
            if reply_message:
                reply_user = reply_message.get('from', 'Неизвестный пользователь')
                if reply_user != 'Неизвестный пользователь':
                    user_interactions[user][reply_user] += 1

    # Анализ времени отклика
    if len(users) == 2:
        users_list = list(users)
        user1, user2 = users_list
        last_message_time: Dict[str, datetime] = {user1: None, user2: None}

        for message in messages:
            user = message.get('from', 'Неизвестный пользователь')
            if user not in users:
                continue

            message_date = message.get('date')
            if message_date:
                try:
                    dt = datetime.fromisoformat(message_date)
                except ValueError:
                    continue

                other_user = user2 if user == user1 else user1
                previous_time = last_message_time[other_user]
                if previous_time:
                    response_time = (dt - previous_time).total_seconds()
                    if response_time > 0:
                        response_times[user].append(response_time)

                last_message_time[user] = dt
    else:
        # Для более чем двух пользователей используем общий подход
        last_message_time: Dict[str, datetime] = {}

        for index, message in enumerate(messages):
            user = message.get('from', 'Неизвестный пользователь')
            if user not in users:
                continue

            message_date = message.get('date')
            if message_date:
                try:
                    dt = datetime.fromisoformat(message_date)
                except ValueError:
                    continue

                # Находим последнее сообщение от другого пользователя
                previous_time = None
                for prev_message in reversed(messages[:index]):
                    prev_user = prev_message.get('from', 'Неизвестный пользователь')
                    if prev_user != user and prev_user in users:
                        prev_date = prev_message.get('date')
                        if prev_date:
                            try:
                                prev_dt = datetime.fromisoformat(prev_date)
                                previous_time = prev_dt
                                break
                            except ValueError:
                                continue
                if previous_time:
                    response_time = (dt - previous_time).total_seconds()
                    if response_time > 0:
                        response_times[user].append(response_time)

                last_message_time[user] = dt

    analyzed_data = {
        'cleaned_messages': cleaned_messages,
        'user_message_counts': dict(user_message_counts),
        'user_message_lengths': dict(user_message_lengths),
        'activity_by_hour': dict(sorted(activity_by_hour.items())),
        'activity_by_weekday': dict(sorted(activity_by_weekday.items(), key=lambda x: weekday_order(x[0]))),
        'activity_by_month': dict(sorted(activity_by_month.items(), key=lambda x: month_order(x[0]))),
        'activity_by_time_of_day': dict(sorted(activity_by_time_of_day.items())),
        'word_counter': dict(word_counter),
        'user_interactions': {user: dict(interactions) for user, interactions in user_interactions.items()},
        'response_times': dict(response_times)
    }

    logging.info("Анализ данных завершен.")
    return analyzed_data

def generate_html(data: Dict[str, Any]) -> str:
    """
    Генерирует HTML отчет на основе проанализированных данных.

    :param data: Результаты анализа.
    :return: Строка с HTML контентом.
    """

    total_messages = len(data['cleaned_messages'])
    user_average_lengths = {
        user: round(sum(lengths) / len(lengths), 2) if lengths else 0
        for user, lengths in data['user_message_lengths'].items()
    }
    most_common_words = Counter(data['word_counter']).most_common(50)

    hours = list(data['activity_by_hour'].keys())
    messages_per_hour = list(data['activity_by_hour'].values())

    weekdays = list(data['activity_by_weekday'].keys())
    messages_per_weekday = list(data['activity_by_weekday'].values())

    months = list(data['activity_by_month'].keys())
    messages_per_month = list(data['activity_by_month'].values())

    time_of_day = list(data['activity_by_time_of_day'].keys())
    messages_per_time_of_day = list(data['activity_by_time_of_day'].values())

    user_interactions_labels = list(data['user_interactions'].keys())
    user_interactions_data = [sum(interactions.values()) for interactions in data['user_interactions'].values()]

    response_times_labels = list(data['response_times'].keys())
    response_times_data = [
        round(sum(times) / len(times), 2) if times else 0
        for times in data['response_times'].values()
    ]

    # Начало HTML
    html = f"""
    <!DOCTYPE html>
    <html lang="ru">
    <head>
        <meta charset="UTF-8">
        <title>Статистика Чата</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css">
        <style>
            body {{
                font-family: Arial, sans-serif;
                margin: 20px;
            }}
            table {{
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 40px;
            }}
            th, td {{
                border: 1px solid #ddd;
                padding: 8px;
                text-align: left;
            }}
            th {{
                background-color: #f2f2f2;
            }}
            h2 {{
                color: #333;
            }}
            .chart-container {{
                width: 100%;
                height: 400px;
                margin-bottom: 40px;
            }}
            .summary {{
                margin-bottom: 40px;
            }}
        </style>
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
        <script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
    </head>
    <body>
        <h1>Статистика Чата</h1>

        <div class="summary">
            <p><strong>Общее количество сообщений:</strong> {total_messages}</p>
        </div>

        <h2>Количество Сообщений на Пользователя</h2>
        <table id="userMessagesTable" class="display">
            <thead>
                <tr>
                    <th>Пользователь</th>
                    <th>Количество Сообщений</th>
                </tr>
            </thead>
            <tbody>
    """

    # Таблица количества сообщений на пользователя
    for user, count in sorted(data['user_message_counts'].items(), key=lambda x: x[1], reverse=True):
        html += f"""
                <tr>
                    <td>{escape(str(user))}</td>
                    <td>{count}</td>
                </tr>
        """
    html += """
            </tbody>
        </table>

        <h2>Средняя Длина Сообщений на Пользователя</h2>
        <table id="userAverageLengthsTable" class="display">
            <thead>
                <tr>
                    <th>Пользователь</th>
                    <th>Средняя Длина Сообщения</th>
                </tr>
            </thead>
            <tbody>
    """

    # Таблица средней длины сообщений
    for user, avg_len in sorted(user_average_lengths.items(), key=lambda x: x[1], reverse=True):
        html += f"""
                <tr>
                    <td>{escape(str(user))}</td>
                    <td>{avg_len}</td>
                </tr>
        """
    html += """
            </tbody>
        </table>

        <h2>Активность Пользователей</h2>
        <div class="chart-container">
            <canvas id="activityByHourChart"></canvas>
        </div>
        <div class="chart-container">
            <canvas id="activityByWeekdayChart"></canvas>
        </div>
        <div class="chart-container">
            <canvas id="activityByMonthChart"></canvas>
        </div>
        <div class="chart-container">
            <canvas id="activityByTimeOfDayChart"></canvas>
        </div>

        <h2>Частотный Анализ Слов (Топ-50)</h2>
        <table id="wordFrequencyTable" class="display">
            <thead>
                <tr>
                    <th>Слово</th>
                    <th>Частота</th>
                </tr>
            </thead>
            <tbody>
    """

    # Таблица частотного анализа слов
    for word, freq in most_common_words:
        html += f"""
                <tr>
                    <td>{escape(str(word))}</td>
                    <td>{freq}</td>
                </tr>
        """
    html += """
            </tbody>
        </table>

        <h2>Анализ Взаимодействий между Пользователями</h2>
        <div class="chart-container">
            <canvas id="userInteractionsChart"></canvas>
        </div>

        <h2>Анализ Времени Отклика</h2>
        <div class="chart-container">
            <canvas id="responseTimesChart"></canvas>
        </div>

        <script>
            // Данные для графика активности по часам
            const activityByHourCtx = document.getElementById('activityByHourChart').getContext('2d');
            const activityByHourChart = new Chart(activityByHourCtx, {
                type: 'bar',
                data: {
                    labels: {json.dumps(hours)},
                    datasets: [{
                        label: 'Количество Сообщений',
                        data: {json.dumps(messages_per_hour)},
                        backgroundColor: 'rgba(54, 162, 235, 0.6)',
                        borderColor: 'rgba(54, 162, 235, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            title: {
                                display: true,
                                text: 'Часы'
                            }
                        },
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: 'Количество Сообщений'
                            }
                        }
                    }
                }
            });

            // Данные для графика активности по дням недели
            const activityByWeekdayCtx = document.getElementById('activityByWeekdayChart').getContext('2d');
            const activityByWeekdayChart = new Chart(activityByWeekdayCtx, {
                type: 'bar',
                data: {
                    labels: {json.dumps(weekdays)},
                    datasets: [{
                        label: 'Количество Сообщений',
                        data: {json.dumps(messages_per_weekday)},
                        backgroundColor: 'rgba(255, 99, 132, 0.6)',
                        borderColor: 'rgba(255, 99, 132, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            title: {
                                display: true,
                                text: 'Дни Недели'
                            }
                        },
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: 'Количество Сообщений'
                            }
                        }
                    }
                }
            });

            // Данные для графика активности по месяцам
            const activityByMonthCtx = document.getElementById('activityByMonthChart').getContext('2d');
            const activityByMonthChart = new Chart(activityByMonthCtx, {
                type: 'bar',
                data: {
                    labels: {json.dumps(months)},
                    datasets: [{
                        label: 'Количество Сообщений',
                        data: {json.dumps(messages_per_month)},
                        backgroundColor: 'rgba(75, 192, 192, 0.6)',
                        borderColor: 'rgba(75, 192, 192, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            title: {
                                display: true,
                                text: 'Месяцы'
                            }
                        },
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: 'Количество Сообщений'
                            }
                        }
                    }
                }
            });

            // Данные для графика активности по времени суток
            const activityByTimeOfDayCtx = document.getElementById('activityByTimeOfDayChart').getContext('2d');
            const activityByTimeOfDayChart = new Chart(activityByTimeOfDayCtx, {
                type: 'bar',
                data: {
                    labels: {json.dumps(time_of_day)},
                    datasets: [{
                        label: 'Количество Сообщений',
                        data: {json.dumps(messages_per_time_of_day)},
                        backgroundColor: 'rgba(153, 102, 255, 0.6)',
                        borderColor: 'rgba(153, 102, 255, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            title: {
                                display: true,
                                text: 'Время Суток'
                            }
                        },
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: 'Количество Сообщений'
                            }
                        }
                    }
                }
            });

            // Данные для графика взаимодействий между пользователями
            const userInteractionsCtx = document.getElementById('userInteractionsChart').getContext('2d');
            const userInteractionsChart = new Chart(userInteractionsCtx, {
                type: 'bar',
                data: {
                    labels: {json.dumps(user_interactions_labels)},
                    datasets: [{
                        label: 'Количество Взаимодействий',
                        data: {json.dumps(user_interactions_data)},
                        backgroundColor: 'rgba(255, 159, 64, 0.6)',
                        borderColor: 'rgba(255, 159, 64, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            title: {
                                display: true,
                                text: 'Пользователи'
                            }
                        },
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: 'Количество Взаимодействий'
                            }
                        }
                    }
                }
            });

            // Данные для графика времени отклика
            const responseTimesCtx = document.getElementById('responseTimesChart').getContext('2d');
            const responseTimesChart = new Chart(responseTimesCtx, {
                type: 'bar',
                data: {
                    labels: {json.dumps(response_times_labels)},
                    datasets: [{
                        label: 'Среднее Время Отклика (секунды)',
                        data: {json.dumps(response_times_data)},
                        backgroundColor: 'rgba(255, 206, 86, 0.6)',
                        borderColor: 'rgba(255, 206, 86, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    responsive: true,
                    scales: {
                        x: {
                            title: {
                                display: true,
                                text: 'Пользователи'
                            }
                        },
                        y: {
                            beginAtZero: true,
                            title: {
                                display: true,
                                text: 'Среднее Время Отклика (секунды)'
                            }
                        }
                    }
                }
            });

            // Инициализация DataTables
            $(document).ready(function() {
                $('#userMessagesTable').DataTable({
                    "paging": false,
                    "searching": false,
                    "info": false
                });
                $('#userAverageLengthsTable').DataTable({
                    "paging": false,
                    "searching": false,
                    "info": false
                });
                $('#wordFrequencyTable').DataTable({
                    "paging": false,
                    "searching": false,
                    "info": false
                });
            });
        </script>

    </body>
    </html>
    """

    return html

def parse_arguments() -> Tuple[str, str, str]:
    """
    Парсит аргументы командной строки.

    :return: Кортеж с путями к входному JSON файлу, выходному очищенному JSON и выходному HTML файлу.
    """
    parser = argparse.ArgumentParser(description="Анализатор чата и генерация статистики.")
    parser.add_argument(
        '-i', '--input',
        required=True,
        help="Путь к входному JSON файлу чата."
    )
    parser.add_argument(
        '-j', '--json_output',
        required=True,
        help="Путь для сохранения очищенного JSON файла."
    )
    parser.add_argument(
        '-o', '--html_output',
        required=True,
        help="Путь для сохранения HTML отчета."
    )
    args = parser.parse_args()

    return args.input, args.json_output, args.html_output

def main():
    input_file, output_json, output_html = parse_arguments()

    data = load_json(input_file)
    if data:
        analyzed_data = analyze_data(data)
        save_json(analyzed_data['cleaned_messages'], output_json)
        html_content = generate_html(analyzed_data)
        save_html(html_content, output_html)
    else:
        logging.error("Завершение работы из-за ошибки при загрузке данных.")

if __name__ == "__main__":
    main()                         (куда писать путь "C:\Users\Admin\Downloads\AyuGram Desktop\ChatExport_2024-09-23 (2)\result.json")