Загрузка данных


import psycopg2
from psycopg2 import sql, OperationalError
import os
from dotenv import load_dotenv
from datetime import datetime
from typing import Optional, List, Dict, Any

# Загрузка переменных окружения
load_dotenv()

class DatabaseManager:
    """Класс для управления подключением к БД"""
    
    def __init__(self):
        self.conn = None
        self.cursor = None
        self.connect()
    
    def connect(self) -> bool:
        """Установка соединения с БД"""
        try:
            self.conn = psycopg2.connect(
                host=os.getenv('DB_HOST', 'localhost'),
                port=os.getenv('DB_PORT', '5432'),
                database=os.getenv('DB_NAME', 'samoletiki'),
                user=os.getenv('DB_USER', 'postgres'),
                password=os.getenv('DB_PASSWORD', '')
            )
            self.cursor = self.conn.cursor()
            print("✓ Подключение к базе данных 'samoletiki' установлено")
            return True
        except OperationalError as e:
            print(f"✗ Ошибка подключения к БД: {e}")
            return False
    
    def close(self):
        """Закрытие соединения"""
        if self.cursor:
            self.cursor.close()
        if self.conn:
            self.conn.close()
            print("✓ Соединение с БД закрыто")
    
    def commit(self):
        """Подтверждение транзакции"""
        if self.conn:
            self.conn.commit()
    
    def rollback(self):
        """Откат транзакции"""
        if self.conn:
            self.conn.rollback()


class AviationSystem:
    """Основной класс системы управления авиаперевозками"""
    
    def __init__(self):
        self.db = DatabaseManager()
        self.current_passenger = None
    
    def passenger_login(self, passport_number: str) -> Optional[Dict[str, Any]]:
        """Авторизация пассажира по номеру паспорта"""
        try:
            query = """
                SELECT num_pasp, fio, adress, phone 
                FROM passzhir 
                WHERE num_pasp = %s
            """
            self.db.cursor.execute(query, (passport_number,))
            passenger = self.db.cursor.fetchone()
            
            if passenger:
                return {
                    'passport': passenger[0],
                    'name': passenger[1],
                    'address': passenger[2],
                    'phone': passenger[3]
                }
            return None
        except Exception as e:
            print(f"Ошибка при авторизации: {e}")
            return None
    
    def get_passenger_flights(self, passport_number: str) -> List[Dict[str, Any]]:
        """Получение всех рейсов пассажира"""
        try:
            query = """
                SELECT 
                    r.num_reis,
                    r.date_time_vilet,
                    r.reis_otmen,
                    m.aer_vilet,
                    m.aer_pril,
                    m.price_bilet,
                    m.prodol_poleta,
                    s.model AS model_samoleta,
                    s.bort_num,
                    k.fio AS fio_komandira
                FROM bilet b
                JOIN reis r ON b.num_reis = r.num_reis
                JOIN marshrut m ON r.num_mar = m.num_mar
                JOIN samolet s ON r.bort_num = s.bort_num
                JOIN komandir k ON s.lich_num_komand = k.lich_num
                WHERE b.num_pasp = %s
                ORDER BY r.date_time_vilet
            """
            self.db.cursor.execute(query, (passport_number,))
            flights = self.db.cursor.fetchall()
            
            result = []
            for flight in flights:
                result.append({
                    'flight_number': flight[0],
                    'departure_time': flight[1],
                    'is_canceled': flight[2],
                    'departure_airport': flight[3],
                    'arrival_airport': flight[4],
                    'ticket_price': float(flight[5]),
                    'duration_min': flight[6],
                    'aircraft_model': flight[7],
                    'tail_number': flight[8],
                    'captain_name': flight[9]
                })
            return result
        except Exception as e:
            print(f"Ошибка при получении рейсов: {e}")
            return []
    
    def get_all_routes(self) -> List[tuple]:
        """Получение всех маршрутов"""
        try:
            self.db.cursor.execute("""
                SELECT num_mar, aer_vilet, aer_pril, price_bilet, prodol_poleta
                FROM marshrut
                ORDER BY num_mar
            """)
            return self.db.cursor.fetchall()
        except Exception as e:
            print(f"Ошибка: {e}")
            return []
    
    def get_all_flights(self) -> List[tuple]:
        """Получение всех рейсов"""
        try:
            self.db.cursor.execute("""
                SELECT r.num_reis, m.aer_vilet, m.aer_pril,
                       r.date_time_vilet, r.reis_otmen, s.model
                FROM reis r
                JOIN marshrut m ON r.num_mar = m.num_mar
                JOIN samolet s ON r.bort_num = s.bort_num
                ORDER BY r.date_time_vilet
            """)
            return self.db.cursor.fetchall()
        except Exception as e:
            print(f"Ошибка: {e}")
            return []
    
    def get_aircraft_info(self) -> List[tuple]:
        """Получение информации о самолетах"""
        try:
            self.db.cursor.execute("""
                SELECT s.bort_num, s.model, s.date_izgot, 
                       s.srok_ecspluat, s.gotovnosty, k.fio,
                       EXTRACT(YEAR FROM AGE(CURRENT_DATE, s.date_izgot)) as age
                FROM samolet s
                JOIN komandir k ON s.lich_num_komand = k.lich_num
                ORDER BY s.bort_num
            """)
            return self.db.cursor.fetchall()
        except Exception as e:
            print(f"Ошибка: {e}")
            return []
    
    def get_flight_passengers(self, flight_number: int) -> List[tuple]:
        """Получение списка пассажиров на рейсе"""
        try:
            self.db.cursor.execute("""
                SELECT p.num_pasp, p.fio, p.phone
                FROM bilet b
                JOIN passzhir p ON b.num_pasp = p.num_pasp
                WHERE b.num_reis = %s
            """, (flight_number,))
            return self.db.cursor.fetchall()
        except Exception as e:
            print(f"Ошибка: {e}")
            return []
    
    def buy_ticket(self, passport_number: str, flight_number: int) -> bool:
        """Покупка билета на рейс"""
        try:
            # Проверяем, существует ли рейс и не отменен ли он
            self.db.cursor.execute("""
                SELECT reis_otmen FROM reis WHERE num_reis = %s
            """, (flight_number,))
            flight = self.db.cursor.fetchone()
            
            if not flight:
                print("✗ Рейс не найден")
                return False
            
            if flight[0]:
                print("✗ Нельзя купить билет на отмененный рейс")
                return False
            
            # Проверяем, не куплен ли уже билет
            self.db.cursor.execute("""
                SELECT * FROM bilet WHERE num_pasp = %s AND num_reis = %s
            """, (passport_number, flight_number))
            
            if self.db.cursor.fetchone():
                print("✗ Билет на этот рейс уже куплен")
                return False
            
            # Покупаем билет
            self.db.cursor.execute("""
                INSERT INTO bilet (num_pasp, num_reis) VALUES (%s, %s)
            """, (passport_number, flight_number))
            
            self.db.commit()
            print("✓ Билет успешно куплен!")
            return True
            
        except Exception as e:
            self.db.rollback()
            print(f"Ошибка при покупке билета: {e}")
            return False


class ConsoleApp:
    """Консольное приложение для работы с системой"""
    
    def __init__(self):
        self.system = AviationSystem()
    
    def clear_screen(self):
        """Очистка экрана"""
        os.system('cls' if os.name == 'nt' else 'clear')
    
    def print_header(self, title: str):
        """Вывод заголовка"""
        print("=" * 90)
        print(f"  {title}")
        print("=" * 90)
    
    def print_menu(self) -> str:
        """Вывод главного меню"""
        self.clear_screen()
        self.print_header("АВИАКОМПАНИЯ 'ПОЛЕТ' - Система управления рейсами")
        
        if self.system.current_passenger:
            print(f"\n  Добро пожаловать, {self.system.current_passenger['name']}!")
            print(f"  Номер паспорта: {self.system.current_passenger['passport']}\n")
            print("  ГЛАВНОЕ МЕНЮ:")
            print("  1. Мои рейсы")
            print("  2. Все маршруты")
            print("  3. Все рейсы")
            print("  4. Информация о самолетах")
            print("  5. Купить билет")
            print("  6. Сменить пассажира")
            print("  0. Выход")
        else:
            print("\n  ГЛАВНОЕ МЕНЮ:")
            print("  1. Вход по номеру паспорта")
            print("  2. Все маршруты")
            print("  3. Все рейсы")
            print("  4. Информация о самолетах")
            print("  0. Выход")
        
        print("\n" + "-" * 90)
        choice = input("  Выберите пункт меню: ")
        return choice
    
    def display_flights_table(self, flights: List[Dict[str, Any]]):
        """Отображение рейсов в виде таблицы"""
        if not flights:
            print("\n  ✗ Рейсы не найдены")
            return
        
        print("\n  СПИСОК РЕЙСОВ:")
        print("  " + "-" * 100)
        print(f"  {'№ рейса':<10} {'Откуда':<15} {'Куда':<15} {'Дата и время':<22} {'Статус':<12} {'Самолет':<15}")
        print("  " + "-" * 100)
        
        for flight in flights:
            status = "ОТМЕНЕН" if flight['is_canceled'] else "ВЫПОЛНЯЕТСЯ"
            if isinstance(flight['departure_time'], datetime):
                dep_time = flight['departure_time'].strftime("%d.%m.%Y %H:%M")
            else:
                dep_time = str(flight['departure_time'])[:16]
            
            print(f"  {flight['flight_number']:<10} {flight['departure_airport']:<15} "
                  f"{flight['arrival_airport']:<15} {dep_time:<22} {status:<12} "
                  f"{flight['aircraft_model']:<15}")
        
        print("  " + "-" * 100)
    
    def display_detailed_flight_info(self, flights: List[Dict[str, Any]]):
        """Детальное отображение информации о рейсах"""
        if not flights:
            print("\n  ✗ У вас нет купленных билетов на рейсы")
            return
        
        print("\n  ПОДРОБНАЯ ИНФОРМАЦИЯ О ВАШИХ РЕЙСАХ:")
        print("  " + "=" * 90)
        
        for i, flight in enumerate(flights, 1):
            print(f"\n  Рейс #{i}")
            print(f"  {'─' * 86}")
            print(f"  Номер рейса:          {flight['flight_number']}")
            print(f"  Маршрут:              {flight['departure_airport']} → {flight['arrival_airport']}")
            
            if isinstance(flight['departure_time'], datetime):
                dep_time = flight['departure_time'].strftime('%d.%m.%Y %H:%M')
            else:
                dep_time = str(flight['departure_time'])
            
            print(f"  Дата и время вылета:  {dep_time}")
            print(f"  Длительность полета:  {flight['duration_min']} мин. ({flight['duration_min'] // 60} ч {flight['duration_min'] % 60} мин)")
            print(f"  Цена билета:          {flight['ticket_price']:.2f} руб.")
            print(f"  Статус рейса:         {'❌ ОТМЕНЕН' if flight['is_canceled'] else '✅ ВЫПОЛНЯЕТСЯ'}")
            print(f"  Самолет:              {flight['aircraft_model']} (борт {flight['tail_number']})")
            print(f"  Командир корабля:     {flight['captain_name']}")
        
        print("\n" + "=" * 90)
    
    def display_routes(self):
        """Отображение всех маршрутов"""
        routes = self.system.get_all_routes()
        if not routes:
            print("\n  ✗ Маршруты не найдены")
            return
        
        print("\n  ДОСТУПНЫЕ МАРШРУТЫ:")
        print("  " + "-" * 90)
        print(f"  {'№':<5} {'Откуда':<20} {'Куда':<20} {'Цена (руб)':<15} {'Длительность':<15}")
        print("  " + "-" * 90)
        
        for route in routes:
            duration = f"{route[4]} мин ({route[4] // 60}ч {route[4] % 60}мин)"
            print(f"  {route[0]:<5} {route[1]:<20} {route[2]:<20} {route[3]:<15.2f} {duration:<15}")
        
        print("  " + "-" * 90)
    
    def display_all_flights(self):
        """Отображение всех рейсов"""
        flights = self.system.get_all_flights()
        if not flights:
            print("\n  ✗ Рейсы не найдены")
            return
        
        print("\n  ВСЕ РЕЙСЫ:")
        print("  " + "-" * 110)
        print(f"  {'№ рейса':<10} {'Откуда':<15} {'Куда':<15} {'Дата и время вылета':<22} {'Статус':<12} {'Самолет':<20}")
        print("  " + "-" * 110)
        
        for flight in flights:
            status = "ОТМЕНЕН" if flight[4] else "ВЫПОЛНЯЕТСЯ"
            if isinstance(flight[3], datetime):
                dep_time = flight[3].strftime("%d.%m.%Y %H:%M")
            else:
                dep_time = str(flight[3])[:16]
            
            print(f"  {flight[0]:<10} {flight[1]:<15} {flight[2]:<15} {dep_time:<22} {status:<12} {flight[5]:<20}")
        
        print("  " + "-" * 110)
        
        # Предложение посмотреть пассажиров рейса
        show = input("\n  Показать пассажиров конкретного рейса? (да/нет): ").lower()
        if show in ['да', 'yes', 'y', 'д']:
            flight_num = input("  Введите номер рейса: ")
            if flight_num.isdigit():
                self.display_flight_passengers(int(flight_num))
    
    def display_flight_passengers(self, flight_number: int):
        """Отображение пассажиров на рейсе"""
        passengers = self.system.get_flight_passengers(flight_number)
        
        if not passengers:
            print(f"\n  ✗ На рейс №{flight_number} нет пассажиров или рейс не найден")
            return
        
        print(f"\n  ПАССАЖИРЫ НА РЕЙСЕ №{flight_number}:")
        print("  " + "-" * 80)
        print(f"  {'Номер паспорта':<15} {'ФИО':<35} {'Телефон':<20}")
        print("  " + "-" * 80)
        
        for passenger in passengers:
            print(f"  {passenger[0]:<15} {passenger[1]:<35} {passenger[2]:<20}")
        
        print("  " + "-" * 80)
    
    def display_aircraft_info(self):
        """Отображение информации о самолетах"""
        aircrafts = self.system.get_aircraft_info()
        if not aircrafts:
            print("\n  ✗ Информация о самолетах не найдена")
            return
        
        print("\n  ПАРК САМОЛЕТОВ:")
        print("  " + "=" * 100)
        
        for aircraft in aircrafts:
            status = "ГОТОВ" if aircraft[4] else "НЕ ГОТОВ"
            status_icon = "✅" if aircraft[4] else "❌"
            age = int(aircraft[6]) if aircraft[6] else 0
            
            print(f"\n  ✈️  Бортовой номер: {aircraft[0]}")
            print(f"     Модель:         {aircraft[1]}")
            print(f"     Год выпуска:    {aircraft[2].year if isinstance(aircraft[2], datetime) else aircraft[2][:4]}")
            print(f"     Возраст:        {age} лет")
            print(f"     Срок службы:    {aircraft[3]} лет")
            print(f"     Осталось лет:   {aircraft[3] - age} лет")
            print(f"     Состояние:      {status_icon} {status}")
            print(f"     Командир:       {aircraft[5]}")
            print("  " + "-" * 60)
        
        print("=" * 100)
    
    def login(self):
        """Авторизация пассажира"""
        self.clear_screen()
        self.print_header("ВХОД В СИСТЕМУ")
        
        print("\n  Для входа в систему введите номер паспорта")
        print("  (например: MP1234567)\n")
        
        passport = input("  Номер паспорта: ").strip()
        
        if not passport:
            print("\n  ✗ Номер паспорта не может быть пустым")
            input("\n  Нажмите Enter для продолжения...")
            return False
        
        passenger = self.system.passenger_login(passport)
        
        if passenger:
            self.system.current_passenger = passenger
            print(f"\n  ✓ Авторизация успешна!")
            print(f"  Добро пожаловать, {passenger['name']}!")
            input("\n  Нажмите Enter для продолжения...")
            return True
        else:
            print(f"\n  ✗ Пассажир с номером паспорта {passport} не найден")
            print("  Возможно, вам нужно зарегистрироваться в системе.")
            input("\n  Нажмите Enter для продолжения...")
            return False
    
    def logout(self):
        """Смена текущего пассажира"""
        self.system.current_passenger = None
        print("\n  ✓ Вы вышли из системы")
        input("\n  Нажмите Enter для продолжения...")
    
    def show_my_flights(self):
        """Отображение рейсов текущего пассажира"""
        if not self.system.current_passenger:
            print("\n  ✗ Вы не авторизованы!")
            input("\n  Нажмите Enter для продолжения...")
            return
        
        flights = self.system.get_passenger_flights(self.system.current_passenger['passport'])
        
        if flights:
            self.display_flights_table(flights)
            show_details = input("\n  Показать детальную информацию? (да/нет): ").lower()
            if show_details in ['да', 'yes', 'y', 'д']:
                self.display_detailed_flight_info(flights)
        else:
            print("\n  ✗ У вас нет купленных билетов на рейсы")
        
        input("\n  Нажмите Enter для продолжения...")
    
    def buy_ticket(self):
        """Покупка билета"""
        if not self.system.current_passenger:
            print("\n  ✗ Вы не авторизованы!")
            input("\n  Нажмите Enter для продолжения...")
            return
        
        self.clear_screen()
        self.print_header("ПОКУПКА БИЛЕТА")
        
        print(f"\n  Пассажир: {self.system.current_passenger['name']}")
        print(f"  Номер паспорта: {self.system.current_passenger['passport']}\n")
        
        # Показываем доступные рейсы
        flights = self.system.get_all_flights()
        if not flights:
            print("  ✗ Нет доступных рейсов")
            input("\n  Нажмите Enter для продолжения...")
            return
        
        print("  ДОСТУПНЫЕ РЕЙСЫ:")
        print("  " + "-" * 100)
        print(f"  {'№ рейса':<10} {'Откуда':<15} {'Куда':<15} {'Дата и время':<22} {'Статус':<12}")
        print("  " + "-" * 100)
        
        for flight in flights:
            if not flight[4]:  # Только неотмененные рейсы
                status = "ДОСТУПЕН"
                dep_time = flight[3].strftime("%d.%m.%Y %H:%M") if isinstance(flight[3], datetime) else str(flight[3])[:16]
                print(f"  {flight[0]:<10} {flight[1]:<15} {flight[2]:<15} {dep_time:<22} {status:<12}")
        
        print("  " + "-" * 100)
        
        flight_num = input("\n  Введите номер рейса для покупки: ")
        
        if flight_num.isdigit():
            if self.system.buy_ticket(self.system.current_passenger['passport'], int(flight_num)):
                pass
        else:
            print("  ✗ Неверный номер рейса")
        
        input("\n  Нажмите Enter для продолжения...")
    
    def run(self):
        """Запуск приложения"""
        print("\n  Загрузка приложения...")
        
        if not self.system.db.conn:
            print("\n  ✗ Не удалось подключиться к базе данных 'samoletiki'")
            print("  Проверьте настройки подключения в файле .env")
            return
        
        while True:
            choice = self.print_menu()
            
            if choice == '1':
                if self.system.current_passenger:
                    self.show_my_flights()
                else:
                    self.login()
            
            elif choice == '2':
                self.display_routes()
                input("\n  Нажмите Enter для продолжения...")
            
            elif choice == '3':
                self.display_all_flights()
                input("\n  Нажмите Enter для продолжения...")
            
            elif choice == '4':
                self.display_aircraft_info()
                input("\n  Нажмите Enter для продолжения...")
            
            elif choice == '5' and self.system.current_passenger:
                self.buy_ticket()
            
            elif choice == '6' and self.system.current_passenger:
                self.logout()
            
            elif choice == '0':
                self.clear_screen()
                print("\n  Спасибо за использование системы!")
                print("  До свидания!\n")
                break
            
            else:
                print("\n  ✗ Неверный выбор. Попробуйте снова.")
                input("\n  Нажмите Enter для продолжения...")


# Точка входа
if __name__ == "__main__":
    app = ConsoleApp()
    app.run()