import os
import json
import uuid
import platform
import re
import subprocess

from rich import box
from rich import print
from rich.console import Console
from rich.prompt import Prompt
from rich.table import Table

from clir.utils.config import verify_clipboard_tool_installation
from clir.utils.core import remove_tag_if_no_commands, replace_arguments, transform_commands_to_json
from clir.utils.db import (
    get_commands_db,
    get_tags_db,
    insert_command_db,
    modify_command_db,
    remove_command_db,
    verify_command_id_exists,
    verify_command_id_tag_relation_exists,
)

class Command:
    def __init__(self, command: str = "", description: str = "", tag: str = ""):
        self.command = command
        self.description = description
        self.tag = tag
        self.uid = uuid.uuid4()

    def __str__(self):
        return f"{self.command} {self.description} {self.tag}"
    
    def __repr__(self):
        return f"{self.command} {self.description} {self.tag}"

    def save_command(self):
        command = self.command
        desc = self.description
        tag = self.tag

        if not tag:
            tag = "clir"

        insert_command_db(command, desc, tag)

        print(f'Command saved successfuly')


#Create class Table
class CommandTable:
    def __init__(self, tag: str = "", grep: str = ""):
        self.commands = transform_commands_to_json(get_commands_db(tag = tag, grep = grep))
        self.tag = tag
        self.grep = grep

    def __str__(self):
        return f"{self.command} {self.description} {self.tag}"

    def __repr__(self):
        return f"{self.command} {self.description} {self.tag}"

    def _get_selected_command(self, uid: str) -> str:
        for command, data in self.commands.items():
            if data["uid"] == uid:
                return command

        return ""

    def show_table(self):

        commands = self.commands
        
        table = Table(show_lines=True, box=box.ROUNDED, style="grey46")
        table.add_column("ID 📇", style="white bold", no_wrap=True)
        table.add_column("Command 💻", style="green bold", no_wrap=True)
        table.add_column("Description 📕", style="magenta bold")
        table.add_column("Tag 🏷️", style="cyan bold")
        
        for indx, command in enumerate(commands):
            desc_len = 50
            command_len = 50
            description = commands[command]["description"]
            split_description = "\n".join([description[i:i+desc_len] for i in range(0, len(description), desc_len)])
            split_command = []
            local_command = ""
            for command_term in command.split(" "):
                if len(local_command) + len(command_term) > command_len:
                    split_command.append(local_command)
                    local_command = command_term + " "
                else:
                    local_command += command_term + " "
                
            split_command.append(local_command)
            
            display_command = " \\ \n".join(split_command)
            table.add_row(str(indx+1), display_command, split_description, commands[command]["tag"])

        console = Console()
        console.print(table)
        print(f"Showing {str(len(commands))} commands")
    
    def run_command(self):
        uid = self.get_command_uid()

        command = self._get_selected_command(uid)
        command = replace_arguments(command)

        if uid and command:
            print(f'[bold green]Running command:[/bold green] {command}')
            subprocess.run(command, shell=True, check=False)
            try:
                subprocess.Popen(
                    ['bash', '-ic', 'set -o history; history -s "$1"', '_', command],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                )
            except OSError:
                pass
    
    def copy_command(self):
        uid = self.get_command_uid()

        command = self._get_selected_command(uid)

        if uid:
            print(f'Copying command: {command}')
            if platform.system() == "Darwin":
                # Verify that pbcopy is installed
                if verify_clipboard_tool_installation(package = "pbcopy"):
                    try:
                        subprocess.run(["pbcopy"], input=command, text=True, check=True)
                    except (subprocess.CalledProcessError, OSError):
                        print("pbcopy failed to copy the command. Please verify clipboard access and try again")
                        return
                else:
                    print("pbcopy is not installed, this command needs pbcopy to work properly")
                    return
            elif platform.system() == "Linux" or platform.system().startswith("CYGWIN"):
                # Verify that xclip is installed
                if verify_clipboard_tool_installation(package = "xclip"):
                    try:
                        subprocess.run(
                            ["xclip", "-selection", "clipboard"],
                            input=command,
                            text=True,
                            check=True,
                        )
                    except (subprocess.CalledProcessError, OSError):
                        print("xclip failed to copy the command. Please verify clipboard access and try again")
                        return
                else:
                    print("xclip is not installed, this command needs xclip to work properly")
                    return
            else:
                print("OS not supported")
    
    def show_tags(self):
        current_commands = self.commands

        tags = []
        for command in current_commands:
            tags.append(current_commands[command]["tag"])
        
        tags = list(dict.fromkeys(tags))

        table = Table(show_lines=True, box=box.ROUNDED, style="grey46")
        table.add_column("Tags 🏷️", style="cyan bold")
        
        for tag in tags:
            table.add_row(tag)

        console = Console()
        console.print(table)
        print(f"Showing {str(len(tags))} tags")

    def export_commands(self):
        try:
            with open("commands.json", "w") as commands_file:
                json.dump(self.commands, commands_file)
            print(f"[bold green]Commands exported succesfully[/bold green] to {os.getcwd()}/commands.json")
        except OSError:
            print("[bold red]Commands could not be exported[/bold red]")
            raise
    
    def import_commands(self, import_file_path: str = ""):
        if import_file_path:
            import_commands = ""
            existing_commands = self.commands


            if os.path.exists(import_file_path):
                try:
                    with open(import_file_path, 'r') as import_file:
                        print(f"Importing commands from {import_file_path}...")
                        import_commands =  json.load(import_file)
                except (OSError, json.JSONDecodeError) as exc:
                    raise ValueError(
                        f"An error occurred while reading the file '{import_file_path}': {exc}"
                    ) from exc
            else:
                raise FileNotFoundError(f"File '{import_file_path}' could not be found")


            for command, data in import_commands.items():
                if command in existing_commands.keys() \
                and data['description'] == existing_commands[command]["description"] \
                and data['tag'] == existing_commands[command]["tag"]:
                    print(f"No changes detected in command [bold green]{command}[/bold green]. Command not imported.")
                    print("---")

                elif command in existing_commands.keys() \
                and (data['description'] != existing_commands[command]["description"] or data['tag'] != existing_commands[command]["tag"]):
                    import_choice = ""
                    print("The command [bold green]{command}[/bold green] already exists in the database:")
                    print(data)
                    table = Table(show_lines=True, box=box.ROUNDED, style="grey46")
                    table.add_column("", style="white bold", no_wrap=True)
                    table.add_column("Import", style="green bold", no_wrap=True)
                    table.add_column("Database", style="magenta bold")
                    table.add_row("Command", command, command)
                    table.add_row("Description", data['description'], existing_commands[command]["description"])
                    table.add_row("Tag", data['tag'], existing_commands[command]["tag"])
                    table.add_row("Creation date", data['creation_date'], existing_commands[command]["creation_date"])
                    table.add_row("Laste modification date", data['last_modif_date'], existing_commands[command]["last_modif_date"])
                    console = Console()
                    console.print(table)

                    while import_choice != "n" and import_choice != "y":
                        import_choice = Prompt.ask("Do you want to replace the existing command? (y/n)").lower()

                    if import_choice == "y":
                        print(f"Importing command: {command}")
                        print(f"Description: {data['description']}")
                        print(f"Tag: {data['tag']}")
                        print(f"Creation date: {data['creation_date']}")
                        print(f"Last modification date: {data['last_modif_date']}")
                        modify_command_db(command, data['description'], data['tag'], data['creation_date'], data['last_modif_date'])
                        print("---")
                    elif import_choice == "n":
                        print("Keeping the already existing command.")
                
                else:
                    print(f"Importing command: {command}")
                    print(f"Description: {data['description']}")
                    print(f"Tag: {data['tag']}")
                    print(f"Creation date: {data['creation_date']}")
                    print(f"Last modification date: {data['last_modif_date']}")
                    insert_command_db(command, data['description'], data['tag'], data['creation_date'], data['last_modif_date'])
                    print("---")

            print("Import complete")
        else:
            print("No file was passed to import")

    # Create a function that deletes a command when passing its uid
    def remove_command(self):
        uids = self.get_command_uids()
        tag_uids = [tag[0] for tag in get_tags_db()]
        uid_to_command = {value["uid"]: command for command, value in self.commands.items()}

        for uid in uids:
            command_name = uid_to_command.get(uid, "unknown")
            remove_command_db(uid)

            if verify_command_id_exists(uid):
                print(f'Command [bold green]{command_name}[/bold green] not removed.')
            elif not verify_command_id_exists(uid) and verify_command_id_tag_relation_exists(uid):
                print(
                    f'Command [bold green]{command_name}[/bold green] '
                    'removed successfuly but relation to tag not removed.'
                )
            elif not verify_command_id_exists(uid) and not verify_command_id_tag_relation_exists(uid):
                print(f'Command [bold green]{command_name}[/bold green] removed successfuly.')
        
        remove_tag_if_no_commands(tag_uids)
        



    def _parse_command_ids_input(self, command_ids_input: str):
        selected_indexes = []
        invalid_tokens = []

        for token in command_ids_input.split(','):
            clean_token = token.strip()

            if not clean_token:
                continue

            if '-' in clean_token:
                range_match = re.fullmatch(r'(\d+)\s*-\s*(\d+)', clean_token)
                if not range_match:
                    invalid_tokens.append(clean_token)
                    continue

                start = int(range_match.group(1))
                end = int(range_match.group(2))

                if start > end:
                    invalid_tokens.append(clean_token)
                    continue

                for index in range(start, end + 1):
                    selected_indexes.append(index)
                continue

            if clean_token.isdigit():
                selected_indexes.append(int(clean_token))
            else:
                invalid_tokens.append(clean_token)

        deduped_indexes = []
        seen = set()
        for index in selected_indexes:
            if index not in seen:
                deduped_indexes.append(index)
                seen.add(index)

        valid_indexes = []
        for index in deduped_indexes:
            if index < 1 or index > len(self.commands):
                invalid_tokens.append(str(index))
                continue
            valid_indexes.append(index)

        return valid_indexes, invalid_tokens

    def get_command_uids(self):
        self.show_table()

        command_ids_input = Prompt.ask("Enter command ID(s), comma-separated and/or ranges (e.g. 1,3-5), or all")

        while True:
            if command_ids_input.strip().lower() == "all":
                return [self.commands[command]["uid"] for command in self.commands]

            valid_indexes, invalid_tokens = self._parse_command_ids_input(command_ids_input)

            if valid_indexes:
                if invalid_tokens:
                    print(f"Ignoring invalid IDs: {', '.join(invalid_tokens)}")

                return [self.commands[list(self.commands.keys())[index - 1]]["uid"] for index in valid_indexes]

            print("ID not valid")
            command_ids_input = Prompt.ask("Enter command ID(s), comma-separated and/or ranges (e.g. 1,3-5), or all")

    def get_command_uid(self):
        self.show_table()
        command_id = Prompt.ask("Enter command ID")

        try:
            while int(command_id) > len(self.commands) or int(command_id) < 1:
                print("ID not valid")
                command_id = Prompt.ask("Enter command ID")
            
            command = self.commands[list(self.commands.keys())[int(command_id)-1]]["uid"]

            return command
        except ValueError:
            print("ID must be an integer")
        
        return ""
