under the templates tab please ensure there is a Create New Template button as well as an edit Template button that calls either 'CreateTemplate.py' or 'Edit Template.py'. Here's the script, do not omit anything, revise upgrade and provide the full script: import sys
import json
import os
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
import pandas as pd
from PyQt6 import QtWidgets, QtGui, QtCore
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QTabWidget, QHBoxLayout,
    QLabel, QLineEdit, QPushButton, QMessageBox, QComboBox,
    QTextEdit, QListWidget, QListWidgetItem, QFileDialog, QSpinBox, QDialog, QColorDialog, QFontDialog
)

# Define file paths
CLIENTS_FILE = 'clients.json'
LINE_ITEMS_FILE = 'line_items.json'
INVOICES_DIR = 'invoices'
EMAIL_CONFIG_FILE = 'email_config.json'
TEMPLATES_FILE = 'templates.json'

# Ensure necessary directories exist
os.makedirs(INVOICES_DIR, exist_ok=True)

# Function to load data from JSON
def load_json(file_path):
    if os.path.exists(file_path):
        with open(file_path, 'r') as f:
            return json.load(f)
    return {}

# Function to save data to JSON
def save_json(data, file_path):
    with open(file_path, 'w') as f:
        json.dump(data, f, indent=4)

# Load initial data
clients = load_json(CLIENTS_FILE)
line_items_data = load_json(LINE_ITEMS_FILE)
email_config = load_json(EMAIL_CONFIG_FILE)
templates = load_json(TEMPLATES_FILE)

# GUI Application
class QuickBooksReplacement(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.clients = load_json(CLIENTS_FILE)
        self.line_items_data = load_json(LINE_ITEMS_FILE)
        self.email_config = load_json(EMAIL_CONFIG_FILE)
        self.templates = load_json(TEMPLATES_FILE)
        self.invoice_counter = self.get_next_invoice_number()  # Initialize invoice counter
        self.init_ui()

    def get_next_invoice_number(self):
        """Gets the next available invoice number."""
        if os.path.exists("invoice_counter.txt"):
            with open("invoice_counter.txt", "r") as f:
                try:
                    counter = int(f.read())
                    return counter + 1
                except ValueError:
                    return 1
        else:
            return 1

    def update_invoice_counter(self):
        """Updates the invoice counter file."""
        with open("invoice_counter.txt", "w") as f:
            f.write(str(self.invoice_counter))

    def init_ui(self):
        self.setWindowTitle("QuickBooks Replacement Tool")
        self.setGeometry(100, 100, 1000, 700)  # Increased width for better layout
        layout = QVBoxLayout()

        # Apply Enhanced Dark Theme
        self.apply_dark_theme()

        # Tab Widget
        self.tabs = QTabWidget()
        layout.addWidget(self.tabs)

        # Tabs
        self.client_tab = ClientManagementTab(self)
        self.line_item_tab = LineItemManagementTab(self)
        self.invoice_tab = InvoiceCreationTab(self)
        self.manage_invoices_tab = ManageInvoicesTab(self)
        self.email_config_tab = EmailConfigTab(self)
        self.templates_tab = TemplateManagementTab(self)  # New Templates Tab

        self.tabs.addTab(self.client_tab, QtGui.QIcon.fromTheme("user-group"), "Clients")
        self.tabs.addTab(self.line_item_tab, QtGui.QIcon.fromTheme("list-add"), "Line Items")
        self.tabs.addTab(self.invoice_tab, QtGui.QIcon.fromTheme("document-new"), "Create Invoice")
        self.tabs.addTab(self.manage_invoices_tab, QtGui.QIcon.fromTheme("folder"), "Manage Invoices")
        self.tabs.addTab(self.email_config_tab, QtGui.QIcon.fromTheme("email-send"), "Email Settings")
        self.tabs.addTab(self.templates_tab, QtGui.QIcon.fromTheme("format-paint"), "Templates")

        # Log Message Area
        self.log_area = QTextEdit(self)
        self.log_area.setReadOnly(True)
        self.log_area.setStyleSheet("""
            background-color: #1E1E1E;
            color: #ffffff;
            font-family: Consolas;
            font-size: 12px;
            border: 1px solid #555555;
        """)
        log_layout = QVBoxLayout()
        log_layout.addWidget(QLabel("Log:"))
        log_layout.addWidget(self.log_area)
        layout.addLayout(log_layout)

        self.setLayout(layout)

    def apply_dark_theme(self):
        dark_stylesheet = """
            /* General Widget Styles */
            QWidget {
                background-color: #121212;
                color: #ffffff;
                font-family: "Segoe UI", sans-serif;
                font-size: 14px;
            }
            QLabel {
                color: #ffffff;
            }
            QLineEdit, QTextEdit, QComboBox, QListWidget, QSpinBox, QPushButton {
                background-color: #1E1E1E;
                border: 1px solid #555555;
                color: #ffffff;
                padding: 5px;
                border-radius: 4px;
            }
            QLineEdit:focus, QTextEdit:focus, QComboBox:focus, QListWidget:focus, QSpinBox:focus, QPushButton:focus {
                border: 2px solid #1E90FF;
            }
            QPushButton {
                background-color: #2D2D30;
            }
            QPushButton:hover {
                background-color: #3E3E42;
            }
            QPushButton:pressed {
                background-color: #5A5A5E;
            }
            QTabWidget::pane {
                border: 1px solid #555555;
            }
            QTabBar::tab {
                background: #2D2D30;
                border: 1px solid #555555;
                padding: 10px;
                margin: 2px;
                color: #ffffff;
                border-radius: 4px;
            }
            QTabBar::tab:selected {
                background: #1E90FF;
                color: #ffffff;
            }
            QMenuBar, QMenu {
                background-color: #121212;
                color: #ffffff;
                font-size: 14px;
            }
            QMenu::item:selected {
                background-color: #3E3E42;
            }
            QListWidget::item:selected {
                background-color: #1E90FF;
                color: #ffffff;
            }
            QHeaderView::section {
                background-color: #2D2D30;
                color: #ffffff;
                padding: 4px;
                border: 1px solid #555555;
            }
            QDialog {
                background-color: #121212;
                color: #ffffff;
            }
            QDialog QPushButton {
                min-width: 80px;
            }
        """
        self.setStyleSheet(dark_stylesheet)

    def log_message(self, message):
        """Logs message to the text area."""
        self.log_area.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}")

class ClientManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Form to add/edit client
        form_group = QtWidgets.QGroupBox("Add New Client")
        form_layout = QHBoxLayout()
        self.client_name_input = QLineEdit(self)
        self.client_name_input.setPlaceholderText("Client Name")
        self.client_email_input = QLineEdit(self)
        self.client_email_input.setPlaceholderText("Client Email")
        self.client_address_input = QLineEdit(self)
        self.client_address_input.setPlaceholderText("Client Address")
        self.add_client_button = QPushButton("Add Client")
        self.add_client_button.clicked.connect(self.add_client)

        form_layout.addWidget(QLabel("Name:"))
        form_layout.addWidget(self.client_name_input)
        form_layout.addWidget(QLabel("Email:"))
        form_layout.addWidget(self.client_email_input)
        form_layout.addWidget(QLabel("Address:"))
        form_layout.addWidget(self.client_address_input)
        form_layout.addWidget(self.add_client_button)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # List of clients
        self.clients_list = QListWidget()
        self.load_clients()
        layout.addWidget(QLabel("Existing Clients:"))
        layout.addWidget(self.clients_list)

        # Buttons to edit and delete clients
        btn_layout = QHBoxLayout()
        self.edit_client_button = QPushButton("Edit Selected")
        self.edit_client_button.clicked.connect(self.edit_client)
        self.delete_client_button = QPushButton("Delete Selected")
        self.delete_client_button.clicked.connect(self.delete_client)
        btn_layout.addWidget(self.edit_client_button)
        btn_layout.addWidget(self.delete_client_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_clients(self):
        self.clients_list.clear()
        for client in self.parent.clients.keys():
            item = QListWidgetItem(client)
            self.clients_list.addItem(item)

    def add_client(self):
        name = self.client_name_input.text().strip()
        email = self.client_email_input.text().strip()
        address = self.client_address_input.text().strip()

        if not name or not email or not address:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        if name in self.parent.clients:
            QMessageBox.warning(self, "Duplicate Client", "Client already exists.")
            return

        self.parent.clients[name] = {
            "email": email,
            "address": address
        }
        save_json(self.parent.clients, CLIENTS_FILE)
        self.parent.log_message(f"Client '{name}' added.")
        self.load_clients()
        self.client_name_input.clear()
        self.client_email_input.clear()
        self.client_address_input.clear()

    def edit_client(self):
        selected = self.clients_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a client to edit.")
            return
        name = selected.text()
        client_data = self.parent.clients[name]

        # Dialog to edit client
        dialog = EditClientDialog(name, client_data, self.parent)
        if dialog.exec():
            new_name, new_email, new_address = dialog.get_data()
            if new_name != name and new_name in self.parent.clients:
                QMessageBox.warning(self, "Duplicate Client", "Client name already exists.")
                return
            # Update client
            del self.parent.clients[name]
            self.parent.clients[new_name] = {
                "email": new_email,
                "address": new_address
            }
            save_json(self.parent.clients, CLIENTS_FILE)
            self.parent.log_message(f"Client '{name}' updated to '{new_name}'.")
            self.load_clients()

    def delete_client(self):
        selected = self.clients_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a client to delete.")
            return
        name = selected.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete client '{name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            del self.parent.clients[name]
            save_json(self.parent.clients, CLIENTS_FILE)
            self.parent.log_message(f"Client '{name}' deleted.")
            self.load_clients()

class EditClientDialog(QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Client")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.email_input = QLineEdit(self)
        self.email_input.setText(self.data["email"])
        self.address_input = QLineEdit(self)
        self.address_input.setText(self.data["address"])

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Email:"))
        self.layout.addWidget(self.email_input)
        self.layout.addWidget(QLabel("Address:"))
        self.layout.addWidget(self.address_input)

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.email_input.text().strip(),
            self.address_input.text().strip()
        )

class LineItemManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Form to add/edit line item
        form_group = QtWidgets.QGroupBox("Add New Line Item")
        form_layout = QHBoxLayout()
        self.item_name_input = QLineEdit(self)
        self.item_name_input.setPlaceholderText("Item Name")
        self.item_price_input = QLineEdit(self)
        self.item_price_input.setPlaceholderText("Item Price")
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)

        form_layout.addWidget(QLabel("Name:"))
        form_layout.addWidget(self.item_name_input)
        form_layout.addWidget(QLabel("Price:"))
        form_layout.addWidget(self.item_price_input)
        form_layout.addWidget(self.add_item_button)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # List of line items
        self.items_list = QListWidget()
        self.load_line_items()
        layout.addWidget(QLabel("Existing Line Items:"))
        layout.addWidget(self.items_list)

        # Buttons to edit and delete items
        btn_layout = QHBoxLayout()
        self.edit_item_button = QPushButton("Edit Selected")
        self.edit_item_button.clicked.connect(self.edit_line_item)
        self.delete_item_button = QPushButton("Delete Selected")
        self.delete_item_button.clicked.connect(self.delete_line_item)
        btn_layout.addWidget(self.edit_item_button)
        btn_layout.addWidget(self.delete_item_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_line_items(self):
        self.items_list.clear()
        for item in self.parent.line_items_data.keys():
            item_obj = QListWidgetItem(item)
            self.items_list.addItem(item_obj)

    def add_line_item(self):
        name = self.item_name_input.text().strip()
        price = self.item_price_input.text().strip()

        if not name or not price:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        try:
            price = float(price)
            if price < 0:
                raise ValueError
        except ValueError:
            QMessageBox.warning(self, "Input Error", "Please enter a valid positive price.")
            return

        if name in self.parent.line_items_data:
            QMessageBox.warning(self, "Duplicate Item", "Line item already exists.")
            return

        self.parent.line_items_data[name] = {"price": price}
        save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
        self.parent.log_message(f"Line item '{name}' added.")
        self.load_line_items()
        self.item_name_input.clear()
        self.item_price_input.clear()

    def edit_line_item(self):
        selected = self.items_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a line item to edit.")
            return
        name = selected.text()
        item_data = self.parent.line_items_data[name]

        # Dialog to edit line item
        dialog = EditLineItemDialog(name, item_data, self.parent)
        if dialog.exec():
            new_name, new_price = dialog.get_data()
            if new_name != name and new_name in self.parent.line_items_data:
                QMessageBox.warning(self, "Duplicate Item", "Line item name already exists.")
                return
            try:
                new_price = float(new_price)
                if new_price < 0:
                    raise ValueError
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid positive price.")
                return
            # Update line item
            del self.parent.line_items_data[name]
            self.parent.line_items_data[new_name] = {"price": new_price}
            save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
            self.parent.log_message(f"Line item '{name}' updated to '{new_name}'.")
            self.load_line_items()

    def delete_line_item(self):
        selected = self.items_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a line item to delete.")
            return
        name = selected.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete line item '{name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            del self.parent.line_items_data[name]
            save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
            self.parent.log_message(f"Line item '{name}' deleted.")
            self.load_line_items()

class EditLineItemDialog(QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Line Item")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.price_input = QLineEdit(self)
        self.price_input.setText(str(self.data["price"]))

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Price:"))
        self.layout.addWidget(self.price_input)

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.price_input.text().strip()
        )

class InvoiceCreationTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.line_items = []
        self.selected_template = None  # For template usage
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Template Selection
        template_layout = QHBoxLayout()
        self.template_combo = QComboBox(self)
        self.load_templates()
        self.template_combo.currentIndexChanged.connect(self.change_template)
        template_layout.addWidget(QLabel("Select Template:"))
        template_layout.addWidget(self.template_combo)
        layout.addLayout(template_layout)

        # Client selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # Add VAT field (optional)
        vat_layout = QHBoxLayout()
        self.vat_input = QLineEdit(self)
        self.vat_input.setPlaceholderText("VAT Number (Optional)")
        vat_layout.addWidget(QLabel("VAT Number:"))
        vat_layout.addWidget(self.vat_input)
        layout.addLayout(vat_layout)

        # Line Items selection
        line_item_layout = QHBoxLayout()
        self.item_combo = QComboBox(self)
        self.load_line_items()
        self.quantity_input = QSpinBox(self)
        self.quantity_input.setMinimum(1)
        self.quantity_input.setValue(1)
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)
        line_item_layout.addWidget(QLabel("Item:"))
        line_item_layout.addWidget(self.item_combo)
        line_item_layout.addWidget(QLabel("Quantity:"))
        line_item_layout.addWidget(self.quantity_input)
        line_item_layout.addWidget(self.add_item_button)
        layout.addLayout(line_item_layout)

        # List to display added line items
        self.added_items_list = QListWidget()
        self.added_items_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        layout.addWidget(QLabel("Added Line Items:"))
        layout.addWidget(self.added_items_list)

        # Discounts and Taxes
        discounts_taxes_layout = QHBoxLayout()
        self.discount_input = QLineEdit(self)
        self.discount_input.setPlaceholderText("Discount (%)")
        self.tax_rate_input = QLineEdit(self)
        self.tax_rate_input.setPlaceholderText("Tax Rate (%)")
        discounts_taxes_layout.addWidget(QLabel("Discount (%):"))
        discounts_taxes_layout.addWidget(self.discount_input)
        discounts_taxes_layout.addWidget(QLabel("Tax Rate (%):"))
        discounts_taxes_layout.addWidget(self.tax_rate_input)
        layout.addLayout(discounts_taxes_layout)

        # Output Format Selection
        output_layout = QHBoxLayout()
        self.output_format_combo = QComboBox(self)
        self.output_format_combo.addItems(["PDF", "Excel"])
        output_layout.addWidget(QLabel("Output Format:"))
        output_layout.addWidget(self.output_format_combo)
        layout.addLayout(output_layout)

        # Button to create invoice
        self.create_invoice_button = QPushButton("Create Invoice")
        self.create_invoice_button.clicked.connect(self.create_invoice)
        layout.addWidget(self.create_invoice_button)

        self.setLayout(layout)

    def load_templates(self):
        self.template_combo.clear()
        templates_list = list(self.parent.templates.keys())
        self.template_combo.addItems(["Default"] + templates_list)
        self.selected_template = "Default"

    def change_template(self, index):
        selected = self.template_combo.currentText()
        if selected == "Default":
            self.selected_template = None
        else:
            self.selected_template = selected

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_line_items(self):
        self.item_combo.clear()
        self.item_combo.addItems(list(self.parent.line_items_data.keys()))

    def add_line_item(self):
        item_name = self.item_combo.currentText()
        quantity = self.quantity_input.value()
        if not item_name:
            QMessageBox.warning(self, "Selection Error", "Please select a line item.")
            return
        price = self.parent.line_items_data[item_name]['price']
        total = price * quantity
        self.line_items.append({
            "item": item_name,
            "quantity": quantity,
            "price": price,
            "total": total
        })
        self.update_added_items_list()

    def update_added_items_list(self):
        self.added_items_list.clear()
        for idx, item in enumerate(self.line_items, 1):
            self.added_items_list.addItem(
                f"{idx}. {item['item']} - Qty: {item['quantity']} - Price: R{item['price']:.2f} - Total: R{item['total']:.2f}"
            )

    def create_invoice(self):
        client_name = self.client_combo.currentText()
        vat_number = self.vat_input.text().strip()
        discount = self.discount_input.text().strip()
        tax_rate = self.tax_rate_input.text().strip()

        if not client_name:
            QMessageBox.warning(self, "Input Error", "Please select a client.")
            return

        if not self.line_items:
            QMessageBox.warning(self, "Input Error", "Please add at least one line item.")
            return

        # Calculate totals
        subtotal = sum(item['total'] for item in self.line_items)
        discount_amount = 0
        if discount:
            try:
                discount_percentage = float(discount)
                discount_amount = subtotal * (discount_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid discount percentage.")
                return

        subtotal_after_discount = subtotal - discount_amount

        tax_amount = 0
        if tax_rate:
            try:
                tax_percentage = float(tax_rate)
                tax_amount = subtotal_after_discount * (tax_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid tax rate percentage.")
                return

        total = subtotal_after_discount + tax_amount

        invoice_date = datetime.now().strftime("%Y-%m-%d")
        client_address = self.parent.clients[client_name]["address"]
        invoice_filename = f"{client_name.replace(' ', '_')}_invoice_{self.parent.invoice_counter}"

        # Prepare DataFrame
        df = pd.DataFrame([
            {"Item": item['item'], "Quantity": item['quantity'], "Price (ZAR)": item['price'], "Total (ZAR)": item['total']}
            for item in self.line_items
        ])

        output_format = self.output_format_combo.currentText()
        if output_format == "PDF":
            self.create_pdf(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        elif output_format == "Excel":
            self.create_excel(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        else:
            QMessageBox.warning(self, "Format Error", "Please select a valid output format.")

        # Reset form
        self.line_items = []
        self.update_added_items_list()
        self.parent.invoice_counter += 1  # Increment invoice counter after successful creation
        self.parent.update_invoice_counter()

    def apply_template_attributes(self, c, template, invoice_details):
        """Applies template attributes like header, footer, and logo to the PDF."""
        if 'header_text' in template:
            c.setFont("Helvetica-Bold", 16)
            c.drawString(50, invoice_details['height'] - 50, template['header_text'])

        if 'footer_text' in template:
            c.setFont("Helvetica", 10)
            c.drawString(50, 50, template['footer_text'])

        if 'logo_path' in template and template['logo_path']:
            if os.path.exists(template['logo_path']):
                c.drawImage(template['logo_path'], invoice_details['width'] - 150, invoice_details['height'] - 100, width=100, height=50)

    def create_pdf(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate PDF invoice."""
        pdf_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.pdf")
        template = self.get_selected_template()

        c = canvas.Canvas(pdf_filename, pagesize=letter)
        width, height = letter

        # Apply Template Attributes
        invoice_details = {'width': width, 'height': height}
        self.apply_template_attributes(c, template, invoice_details)

        # Invoice Header
        c.setFont("Helvetica-Bold", 20)
        c.drawString(50, height - 150, "INVOICE")  # Adjusted position

        # Client Information
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 180, f"Invoice Number: {self.parent.invoice_counter - 1}")  # Displaying counter
        c.drawString(50, height - 200, f"Date: {invoice_date}")
        c.drawString(50, height - 220, f"Client: {client}")
        c.drawString(50, height - 240, f"Address: {client_address}")
        if vat:
            c.drawString(50, height - 260, f"VAT Number: {vat}")

        # Table Headers
        c.setFont("Helvetica-Bold", 12)
        y = height - 300
        c.drawString(50, y, "Item")
        c.drawString(250, y, "Quantity")
        c.drawString(350, y, "Price (ZAR)")
        c.drawString(450, y, "Total (ZAR)")
        y -= 20
        c.line(50, y, 550, y)

        # Table Content
        c.setFont("Helvetica", 12)
        for index, row in df.iterrows():
            y -= 20
            if y < 100:
                c.showPage()
                y = height - 50
                # Re-apply template on new page
                self.apply_template_attributes(c, template, invoice_details)
            c.drawString(50, y, str(row['Item']))
            c.drawString(250, y, str(row['Quantity']))
            c.drawString(350, y, f"R{row['Price (ZAR)']:.2f}")
            c.drawString(450, y, f"R{row['Total (ZAR)']:.2f}")

        # Totals
        y -= 40
        c.setFont("Helvetica-Bold", 12)
        c.drawString(350, y, "Subtotal:")
        c.drawString(450, y, f"R{subtotal:.2f}")
        y -= 20
        if discount > 0:
            c.drawString(350, y, "Discount:")
            c.drawString(450, y, f"R{-discount:.2f}")
            y -= 20
        if tax > 0:
            c.drawString(350, y, "Tax:")
            c.drawString(450, y, f"R{tax:.2f}")
            y -= 20
        c.drawString(350, y, "Total:")
        c.drawString(450, y, f"R{total:.2f}")

        c.save()
        self.parent.log_message(f"Invoice saved as {pdf_filename}")
        self.parent.manage_invoices_tab.load_invoices()
        self.send_email_invoice(client, pdf_filename)

    def create_excel(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate Excel invoice."""
        excel_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.xlsx")
        template = self.get_selected_template()

        writer = pd.ExcelWriter(excel_filename, engine='openpyxl')
        df.to_excel(writer, index=False, sheet_name='Invoice')

        # Access the workbook and worksheet
        workbook = writer.book
        worksheet = writer.sheets['Invoice']

        # Apply Template Attributes (basic - headers)
        current_row = 1
        if 'header_text' in template:
            worksheet.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=4)
            cell = worksheet.cell(row=current_row, column=1)
            cell.value = template['header_text']
            cell.font = cell.font.copy(bold=True, size=16)
            current_row += 1

        # Invoice Information
        worksheet.cell(row=current_row, column=1, value="Invoice Number:")
        worksheet.cell(row=current_row, column=2, value=self.parent.invoice_counter - 1)
        current_row += 1
        worksheet.cell(row=current_row, column=1, value="Invoice Date:")
        worksheet.cell(row=current_row, column=2, value=invoice_date)
        current_row += 1
        worksheet.cell(row=current_row, column=1, value="Client:")
        worksheet.cell(row=current_row, column=2, value=client)
        current_row += 1
        worksheet.cell(row=current_row, column=1, value="Address:")
        worksheet.cell(row=current_row, column=2, value=client_address)
        current_row += 1
        if vat:
            worksheet.cell(row=current_row, column=1, value="VAT Number:")
            worksheet.cell(row=current_row, column=2, value=vat)
            current_row += 1

        # Add totals
        start_row = len(df) + current_row + 2
        worksheet.cell(row=start_row, column=3, value="Subtotal:")
        worksheet.cell(row=start_row, column=4, value=subtotal)
        if discount > 0:
            worksheet.cell(row=start_row + 1, column=3, value="Discount:")
            worksheet.cell(row=start_row + 1, column=4, value=-discount)
        if tax > 0:
            worksheet.cell(row=start_row + 2, column=3, value="Tax:")
            worksheet.cell(row=start_row + 2, column=4, value=tax)
        worksheet.cell(row=start_row + 3, column=3, value="Total:")
        worksheet.cell(row=start_row + 3, column=4, value=total)

        # Apply Template Styles (basic)
        for row in worksheet.iter_rows(min_row=1, max_row=start_row + 3, min_col=1, max_col=4):
            for cell in row:
                cell.font = cell.font.copy(color="FFFFFF")  # White text
                cell.fill = cell.fill.copy(start_color="1E1E1E")  # Dark background

        writer.save()
        self.parent.log_message(f"Invoice saved as {excel_filename}")
        self.parent.manage_invoices_tab.load_invoices()
        self.send_email_invoice(client, excel_filename)

    def get_selected_template(self):
        """Retrieves the selected template's attributes."""
        if self.selected_template and self.selected_template in self.parent.templates:
            return self.parent.templates[self.selected_template]
        return {}

    def send_email_invoice(self, client, invoice_path):
        """Send the invoice via email."""
        # Load email configuration
        email_config = self.parent.email_config
        if not email_config:
            QMessageBox.warning(self, "Email Configuration Missing", "Please configure email settings in the 'Email Settings' tab.")
            return

        sender_email = email_config.get("sender_email")
        sender_password = email_config.get("sender_password")
        smtp_server = email_config.get("smtp_server")
        smtp_port = email_config.get("smtp_port")
        if not all([sender_email, sender_password, smtp_server, smtp_port]):
            QMessageBox.warning(self, "Incomplete Email Configuration", "Please complete all email settings.")
            return

        recipient_email = self.parent.clients[client]['email']
        subject = f"Invoice from QuickBooks Replacement Tool"
        body = f"Dear {client},\n\nPlease find attached your invoice.\n\nThank you!"

        # Create the email message
        msg = MIMEText(body)
        msg['Subject'] = subject
        msg['From'] = sender_email
        msg['To'] = recipient_email

        # Attach the invoice file
        with open(invoice_path, 'rb') as f:
            part = MIMEApplication(f.read(), Name=os.path.basename(invoice_path))
        part['Content-Disposition'] = f'attachment; filename="{os.path.basename(invoice_path)}"'
        msg.attach(part)

        try:
            with smtplib.SMTP(smtp_server, smtp_port) as server:
                server.starttls()
                server.login(sender_email, sender_password)
                server.send_message(msg)
            self.parent.log_message(f"Invoice sent to {client} at {recipient_email}.")
            QMessageBox.information(self, "Email Sent", f"Invoice has been sent to {recipient_email}.")
        except Exception as e:
            QMessageBox.warning(self, "Email Error", f"Failed to send email: {e}")
            self.parent.log_message(f"Failed to send invoice to {client}: {e}")

class ManageInvoicesTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # List of invoices
        self.invoices_list = QListWidget()
        self.invoices_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        self.load_invoices()
        layout.addWidget(QLabel("Invoices:"))
        layout.addWidget(self.invoices_list)

        # Buttons to view, resend, and delete invoices
        btn_layout = QHBoxLayout()
        self.view_invoice_button = QPushButton("View Invoice")
        self.view_invoice_button.clicked.connect(self.view_invoice)
        self.resend_invoice_button = QPushButton("Resend Invoice")
        self.resend_invoice_button.clicked.connect(self.resend_invoice)
        self.delete_invoice_button = QPushButton("Delete Invoice")
        self.delete_invoice_button.clicked.connect(self.delete_invoice)
        btn_layout.addWidget(self.view_invoice_button)
        btn_layout.addWidget(self.resend_invoice_button)
        btn_layout.addWidget(self.delete_invoice_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_invoices(self):
        self.invoices_list.clear()
        if not os.path.exists(INVOICES_DIR):
            return
        invoices = sorted(os.listdir(INVOICES_DIR), reverse=True)
        for invoice in invoices:
            item = QListWidgetItem(invoice)
            self.invoices_list.addItem(item)

    def get_selected_invoice_path(self):
        selected = self.invoices_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice.")
            return None
        invoice_filename = selected.text()
        invoice_path = os.path.join(INVOICES_DIR, invoice_filename)
        return invoice_path

    def view_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        if not os.path.exists(invoice_path):
            QMessageBox.warning(self, "File Not Found", "The selected invoice file does not exist.")
            return
        QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(os.path.abspath(invoice_path)))

    def resend_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        # Extract client name from filename
        try:
            client_name = invoice_path.split('_invoice_')[0].replace('_', ' ')
            if client_name not in self.parent.clients:
                raise ValueError("Client not found.")
            self.parent.invoice_tab.send_email_invoice(client_name, invoice_path)
        except Exception as e:
            QMessageBox.warning(self, "Resend Error", f"Failed to resend invoice: {e}")
            self.parent.log_message(f"Failed to resend invoice '{invoice_path}': {e}")

    def delete_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete invoice '{os.path.basename(invoice_path)}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                os.remove(invoice_path)
                self.parent.log_message(f"Invoice '{os.path.basename(invoice_path)}' deleted.")
                self.load_invoices()
            except Exception as e:
                QMessageBox.warning(self, "Delete Error", f"Failed to delete invoice: {e}")
                self.parent.log_message(f"Failed to delete invoice '{invoice_path}': {e}")

class EmailConfigTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Email configuration form
        form_group = QtWidgets.QGroupBox("Email Configuration")
        form_layout = QVBoxLayout()
        self.sender_email_input = QLineEdit(self)
        self.sender_email_input.setPlaceholderText("Sender Email")
        self.sender_password_input = QLineEdit(self)
        self.sender_password_input.setPlaceholderText("Sender Password")
        self.sender_password_input.setEchoMode(QLineEdit.EchoMode.Password)
        self.smtp_server_input = QLineEdit(self)
        self.smtp_server_input.setPlaceholderText("SMTP Server (e.g., smtp.office365.com)")
        self.smtp_port_input = QLineEdit(self)
        self.smtp_port_input.setPlaceholderText("SMTP Port (e.g., 587)")

        form_layout.addWidget(QLabel("Sender Email:"))
        form_layout.addWidget(self.sender_email_input)
        form_layout.addWidget(QLabel("Sender Password:"))
        form_layout.addWidget(self.sender_password_input)
        form_layout.addWidget(QLabel("SMTP Server:"))
        form_layout.addWidget(self.smtp_server_input)
        form_layout.addWidget(QLabel("SMTP Port:"))
        form_layout.addWidget(self.smtp_port_input)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # Save button
        self.save_email_config_button = QPushButton("Save Email Settings")
        self.save_email_config_button.clicked.connect(self.save_email_config)
        layout.addWidget(self.save_email_config_button)

        self.setLayout(layout)
        self.load_existing_config()

    def load_existing_config(self):
        config = self.parent.email_config
        if config:
            self.sender_email_input.setText(config.get("sender_email", ""))
            self.sender_password_input.setText(config.get("sender_password", ""))
            self.smtp_server_input.setText(config.get("smtp_server", ""))
            self.smtp_port_input.setText(str(config.get("smtp_port", "")))

    def save_email_config(self):
        sender_email = self.sender_email_input.text().strip()
        sender_password = self.sender_password_input.text().strip()
        smtp_server = self.smtp_server_input.text().strip()
        smtp_port = self.smtp_port_input.text().strip()

        if not sender_email or not sender_password or not smtp_server or not smtp_port:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        try:
            smtp_port = int(smtp_port)
        except ValueError:
            QMessageBox.warning(self, "Input Error", "SMTP Port must be a number.")
            return

        self.parent.email_config = {
            "sender_email": sender_email,
            "sender_password": sender_password,
            "smtp_server": smtp_server,
            "smtp_port": smtp_port
        }
        save_json(self.parent.email_config, EMAIL_CONFIG_FILE)
        self.parent.log_message("Email settings updated.")
        QMessageBox.information(self, "Success", "Email settings have been saved.")

class TemplateManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.selected_template = None
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # List of templates
        self.templates_list = QListWidget()
        self.load_templates()
        layout.addWidget(QLabel("Existing Templates:"))
        layout.addWidget(self.templates_list)

        # Buttons to create, edit, and delete templates
        btn_layout = QHBoxLayout()
        self.create_template_button = QPushButton("Create New Template")
        self.create_template_button.clicked.connect(self.create_template)
        self.edit_template_button = QPushButton("Edit Selected")
        self.edit_template_button.clicked.connect(self.edit_template)
        self.delete_template_button = QPushButton("Delete Selected")
        self.delete_template_button.clicked.connect(self.delete_template)
        btn_layout.addWidget(self.create_template_button)
        btn_layout.addWidget(self.edit_template_button)
        btn_layout.addWidget(self.delete_template_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_templates(self):
        self.templates_list.clear()
        for template in self.parent.templates.keys():
            item = QListWidgetItem(template)
            self.templates_list.addItem(item)

    def create_template(self):
        dialog = CreateEditTemplateDialog(self)
        if dialog.exec():
            template_name, attributes = dialog.get_data()
            if not template_name:
                QMessageBox.warning(self, "Input Error", "Template name cannot be empty.")
                return
            if template_name in self.parent.templates:
                QMessageBox.warning(self, "Duplicate Template", "Template with this name already exists.")
                return
            self.parent.templates[template_name] = attributes
            save_json(self.parent.templates, TEMPLATES_FILE)
            self.parent.log_message(f"Template '{template_name}' created.")
            self.load_templates()

    def edit_template(self):
        selected = self.templates_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a template to edit.")
            return
        template_name = selected.text()
        template_data = self.parent.templates[template_name]

        dialog = CreateEditTemplateDialog(self, template_name, template_data)
        if dialog.exec():
            new_name, attributes = dialog.get_data()
            if not new_name:
                QMessageBox.warning(self, "Input Error", "Template name cannot be empty.")
                return
            if new_name != template_name and new_name in self.parent.templates:
                QMessageBox.warning(self, "Duplicate Template", "Template with this name already exists.")
                return
            # Update template
            del self.parent.templates[template_name]
            self.parent.templates[new_name] = attributes
            save_json(self.parent.templates, TEMPLATES_FILE)
            self.parent.log_message(f"Template '{template_name}' updated to '{new_name}'.")
            self.load_templates()

    def delete_template(self):
        selected = self.templates_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a template to delete.")
            return
        template_name = selected.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete template '{template_name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            del self.parent.templates[template_name]
            save_json(self.parent.templates, TEMPLATES_FILE)
            self.parent.log_message(f"Template '{template_name}' deleted.")
            self.load_templates()

class CreateEditTemplateDialog(QDialog):
    def __init__(self, parent, template_name="", template_data=None):
        super().__init__()
        self.setWindowTitle("Create/Edit Template")
        self.parent = parent
        self.template_name = template_name
        self.template_data = template_data if template_data else {}
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        # Template Name
        self.name_input = QLineEdit(self)
        self.name_input.setPlaceholderText("Template Name")
        self.name_input.setText(self.template_name)
        self.layout.addWidget(QLabel("Template Name:"))
        self.layout.addWidget(self.name_input)

        # Header Text
        self.header_text_input = QTextEdit(self)
        self.header_text_input.setPlaceholderText("Header Text")
        self.header_text_input.setText(self.template_data.get("header_text", ""))
        self.layout.addWidget(QLabel("Header Text:"))
        self.layout.addWidget(self.header_text_input)

        # Footer Text
        self.footer_text_input = QTextEdit(self)
        self.footer_text_input.setPlaceholderText("Footer Text")
        self.footer_text_input.setText(self.template_data.get("footer_text", ""))
        self.layout.addWidget(QLabel("Footer Text:"))
        self.layout.addWidget(self.footer_text_input)

        # Logo Path
        logo_layout = QHBoxLayout()
        self.logo_path_input = QLineEdit(self)
        self.logo_path_input.setPlaceholderText("Logo Path")
        self.logo_path_input.setText(self.template_data.get("logo_path", ""))
        self.browse_logo_button = QPushButton("Browse")
        self.browse_logo_button.clicked.connect(self.browse_logo)
        logo_layout.addWidget(QLabel("Logo:"))
        logo_layout.addWidget(self.logo_path_input)
        logo_layout.addWidget(self.browse_logo_button)
        self.layout.addLayout(logo_layout)

        # Header Font
        header_font_layout = QHBoxLayout()
        self.header_font_button = QPushButton("Select Header Font")
        self.header_font_button.clicked.connect(self.select_header_font)
        self.header_font_label = QLabel(self.template_data.get("header_font", "Default"))
        header_font_layout.addWidget(self.header_font_button)
        header_font_layout.addWidget(QLabel("Selected Font:"))
        header_font_layout.addWidget(self.header_font_label)
        self.layout.addLayout(header_font_layout)

        # Footer Font
        footer_font_layout = QHBoxLayout()
        self.footer_font_button = QPushButton("Select Footer Font")
        self.footer_font_button.clicked.connect(self.select_footer_font)
        self.footer_font_label = QLabel(self.template_data.get("footer_font", "Default"))
        footer_font_layout.addWidget(self.footer_font_button)
        footer_font_layout.addWidget(QLabel("Selected Font:"))
        footer_font_layout.addWidget(self.footer_font_label)
        self.layout.addLayout(footer_font_layout)

        # Header Color
        header_color_layout = QHBoxLayout()
        self.header_color_button = QPushButton("Select Header Color")
        self.header_color_button.clicked.connect(self.select_header_color)
        self.header_color_display = QLabel()
        self.header_color_display.setFixedSize(50, 20)
        header_color = self.template_data.get("header_color", "#FFFFFF")
        self.header_color = QtGui.QColor(header_color)
        self.update_color_display(self.header_color_display, self.header_color)
        header_color_layout.addWidget(self.header_color_button)
        header_color_layout.addWidget(QLabel("Selected Color:"))
        header_color_layout.addWidget(self.header_color_display)
        self.layout.addLayout(header_color_layout)

        # Footer Color
        footer_color_layout = QHBoxLayout()
        self.footer_color_button = QPushButton("Select Footer Color")
        self.footer_color_button.clicked.connect(self.select_footer_color)
        self.footer_color_display = QLabel()
        self.footer_color_display.setFixedSize(50, 20)
        footer_color = self.template_data.get("footer_color", "#FFFFFF")
        self.footer_color = QtGui.QColor(footer_color)
        self.update_color_display(self.footer_color_display, self.footer_color)
        footer_color_layout.addWidget(self.footer_color_button)
        footer_color_layout.addWidget(QLabel("Selected Color:"))
        footer_color_layout.addWidget(self.footer_color_display)
        self.layout.addLayout(footer_color_layout)

        # Save and Cancel Buttons
        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def browse_logo(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Logo", "", "Image Files (*.png *.jpg *.bmp)")
        if file_path:
            self.logo_path_input.setText(file_path)

    def select_header_font(self):
        font, ok = QFontDialog.getFont()
        if ok:
            self.header_font = font
            self.header_font_label.setText(font.family() + ", " + font.pointSize().__str__())

    def select_footer_font(self):
        font, ok = QFontDialog.getFont()
        if ok:
            self.footer_font = font
            self.footer_font_label.setText(font.family() + ", " + font.pointSize().__str__())

    def select_header_color(self):
        color = QColorDialog.getColor(initial=self.header_color, parent=self, title="Select Header Color")
        if color.isValid():
            self.header_color = color
            self.update_color_display(self.header_color_display, self.header_color)

    def select_footer_color(self):
        color = QColorDialog.getColor(initial=self.footer_color, parent=self, title="Select Footer Color")
        if color.isValid():
            self.footer_color = color
            self.update_color_display(self.footer_color_display, self.footer_color)

    def update_color_display(self, label, color):
        label.setStyleSheet(f"background-color: {color.name()};")

    def get_data(self):
        template_name = self.name_input.text().strip()
        attributes = {
            "header_text": self.header_text_input.toPlainText().strip(),
            "footer_text": self.footer_text_input.toPlainText().strip(),
            "logo_path": self.logo_path_input.text().strip(),
            "header_font": self.header_font_label.text(),
            "footer_font": self.footer_font_label.text(),
            "header_color": self.header_color.name(),
            "footer_color": self.footer_color.name()
        }
        return template_name, attributes

class TemplateManagementTab(QWidget):
    # Already defined above, remove duplicate
    pass

# Run the application
if __name__ == "__main__":
    app = QApplication(sys.argv)

    # Set application icon (optional, ensure you have an icon file)
    # app.setWindowIcon(QtGui.QIcon('icon.png'))

    window = QuickBooksReplacement()
    window.show()
    sys.exit(app.exec())