import datetime
import json
import threading
import re

from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress
from rich.prompt import IntPrompt


from market_place_cli.v_cloud_market_cli_common.service_display.main_interface import MainInterface
from market_place_cli.service_interface_request.common_request import get_table_choice, get_table_index
from market_place_cli.service_interface_logic.common import calculate_amount
from market_place_cli.v_cloud_market_cli_common.service.service_common import GlobalState
from market_place_cli.v_cloud_market_cli_common.utils.message_decipher import decrypt_message
from market_place_cli.v_cloud_market_cli_common.utils.server_stream import ServerStream
from market_place_cli.utils.regex import is_image_name
from market_place_cli.utils.string_utils import get_container_memory


class UserServiceLogic:

    def __init__(self):
        self.title = 'User Service'
        self.service_info_cache = {}
        self.main_functions = ['Show Running User Service', 'Show Usable User Service', 'Show Past User Service', 'Show Abort User Service', 'Show Notice Message']
        self.interface_index = 0 
        self.stream = ServerStream()
        self.state = GlobalState()
        self.console = self.state.console
        self.wr = self.state.wallet_request
        self.ur = self.state.user_request
        self.md = self.state.market_display
        self.ud = self.state.user_display
        self.ws = self.state.wallet_service
        self.ms = self.state.market_service
        self.us = self.state.user_service
        self.order_service = self.state.order_service

    @property
    def Name(self):
        return self.title

    @property
    def nonce(self):
        return self.state.get_nonce()

    @property
    def password(self):
        return self.state.get_password()

    @property
    def net_type(self):
        return self.state.get_net_type()

    def StartLogic(self):
        self.console.clear()
        while True:
            try:
                choice = MainInterface.display_service_choice(self.console, self.title, self.main_functions, True)
                if choice == '1':
                    self.interface_index = 1
                    self.show_current_user_service_logic()
                elif choice == '2':
                    self.interface_index = 2
                    self.show_usable_user_service_logic()
                elif choice == '3':
                    self.interface_index = 3
                    self.show_past_user_service_logic()
                elif choice == '4':
                    self.interface_index = 4
                    self.show_abort_user_service_logic()
                elif choice == '5':
                    self.interface_index = 5
                    self.show_notice_message()
                elif choice.lower() == 'b':
                    break
                self.interface_index = 0
            except Exception as e:
                self.console.input(f'User Service Error: {e}\nPress ENTER to retry...')

    def show_current_user_service_logic(self):
        self.show_user_service_page("ServiceRunning")

    def show_usable_user_service_logic(self):
        self.show_user_service_page("ServicePending")

    def show_past_user_service_logic(self):
        self.show_user_service_page("ServiceDone")

    def show_abort_user_service_logic(self):
        self.show_user_service_page("ServiceAbort")

    def show_notice_message(self):
        self.show_user_notice_page("ServiceNotice")

    def access_provider_api_logic(self, user_service_info: dict, index: int):
        try:
            # get decryption private key
            private_key = self.state.get_current_account().privateKey

            provider_host = self.ms.get_provider_host(user_service_info["provider"])

            info = self.us.get_user_service_info(provider_host, user_service_info["id"])
            magic = info["magic"]
            magic_txt = decrypt_message(private_key, magic)
            magic_dict = json.loads(magic_txt)
            if 'Ip' in magic_dict:
                magic_dict.pop('Ip')
            p = Panel.fit(str(magic_dict))
            p.title = "Service Login Information"
            p.title_align = "center"
            self.console.print(p)
            self.console.input("Press ENTER to continue...")
        except Exception as e:
            print(e)
            self.console.input("[bright_red]Failed to get userservice info. Press ENTER to continue...")

    def start_user_service_api_logic(self, order_info: dict, index: int):

        if order_info["serviceStatus"] != "ServicePending":
            self.console.print(f"Order service status is { order_info['status'] }, and cannot start service.")
            self.console.input("Press ENTER to continue...")
            return
        # get provider host
        full_user_service_info = self._get_us_info_with_secret(order_info, index)
        if full_user_service_info is None or "Secret" not in full_user_service_info:
            self.console.print(f"[bright_green]Order {order_info['id']} has invalid magic!")
            self.console.input("Press ENTER to continue...")
            return

        image_name = self.ur.get_image_info(self.us.check_image)
        # get container and host post persistStorage
        serviceOptions = full_user_service_info["serviceOptions"]
        port_configs = self.ur.get_ports(serviceOptions, full_user_service_info["provider_host"], self.us.check_ports)
        env_list = self.ur.get_envs()
        memory = get_container_memory(serviceOptions["resourceUnit"])
        container_config = {
            "name": "container-0",
            "imageName": image_name,
            "ports": port_configs,
            "envs": env_list,
            "resource": {
                "memory": memory
            }
        }
        if "persistStorage" in serviceOptions and serviceOptions["persistStorage"] == "Yes":
            container_config["mountPath"] = self.ur.get_mount_path()
        cmd = self.console.input("Please input command(optional) : ")
        if len(cmd.replace(" ", "")) > 0:
            container_config["command"] = cmd
        args = self.console.input("Please input arguments to (optional) : ")
        if len(args) != 0:
            container_config["args"] = args
        service_config = {
            "name": order_info["id"],
            "containers": [container_config]
        }
        try:
            if not self.us.start_usr_service(service_config, full_user_service_info["Ip"], full_user_service_info["Secret"]):
                self.console.print(f"[bright_red]Failed to start service { order_info['id'] }")
            else:
                self.console.print(f"[bright_green]User Service { order_info['id'] } has been deployed successfully!")
        except Exception as e:
            print(e)
            self.console.print(f"[bright_red]Failed to start service { order_info['id'] }")
        self.console.input("Press ENTER to continue...")

    def tail_service_log_logic(self, order_info: dict, index: int):
        user_service_info = self._get_us_info_with_secret(order_info, index)
        if user_service_info is None or "Secret" not in user_service_info:
            self.console.print(f"[bright_green]Order {order_info['id']} has invalid magic!")
            self.console.input("Press ENTER to continue...")
            return
        url = user_service_info["provider_host"] + "/api/v1/k8s/pod/logs"
        headers = {
            "Connection": "close",
            "secret": user_service_info["Secret"]
        }
        self.console.clear()
        self.console.print("[bright_green]Press ENTER to stop log...")
        log_thread = threading.Thread(target=self.stream.open, args=(url, headers, None, self.read_stream))
        log_thread.daemon = True
        log_thread.start()
        self.console.input("")
        # close stream
        self.stream.close()

    def restart_user_service_logic(self, service_info: dict, index: int):
        try:
            confirm = self.console.input(f'[bright_green]Are you sure to restart the user services(default n) \[y/N]: ').strip().lower()
            if confirm != 'y':
                self.console.input("Canceled. Press ENTER to continue...")
                return
            user_service = self._get_us_info_with_secret(service_info, index)
            result = self.us.restart_usr_service(user_service["provider_host"], user_service["Secret"])
            self.console.print('Success. Service is restarting. Please wait.')
            self.console.input("Press ENTER to continue...")
        except Exception as e:
            print(e)
            self.console.input("Press ENTER to continue...")
            return

    def bind_domain_logic(self, service_info: dict, index: int):
        reg = "^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$"
        try:
            while True:
                domain = self.console.input('[bright_green]Please input the domain name: ')
                if re.match(reg, domain):
                    break
                self.console.print('[bright_red]Invalid domain name. Please try again.')
            container_port = IntPrompt.ask('[bright_green]Please enter the container port')
            while True:
                use_https = self.console.input('[bright_green]Do you want to use https (default Y)[Y/n]: ').strip().lower()
                if use_https in ['y', 'n', '']:
                    break
                self.console.print('[bright_red]Invalid input. Please try again.')
            is_https = False if use_https =='n' else True
            user_service = self._get_us_info_with_secret(service_info, index)
            result = self.us.bind_domain_name(user_service["provider_host"], user_service["Secret"], domain, is_https, container_port)
            self.console.input("Success. Press ENTER to continue...")
        except Exception as e:
            print(e)
            self.console.input("Failed. Press ENTER to continue...")

    def unbind_domain_logic(self, service_info: dict, index: int):
        try:
            # check if domain name already binded
            user_service = self._get_us_info_with_secret(service_info, index)
            status_data = self.us.service_status(user_service["provider_host"], user_service["Secret"])
            if "domainName" in status_data and status_data["domainName"]:
                confirm = self.console.input(f'[bright_green]Are you sure to unbind domain {status_data["domainName"]} from the user service {user_service["id"]} (default n) \[y/N]: ').strip().lower()
                if confirm != 'y':
                    self.console.input("Canceled. Press ENTER to continue...")
                    return
                result = self.us.unbind_domain_name(user_service["provider_host"], user_service["Secret"])
                self.console.input("Success. Press ENTER to continue...")
            else:
                self.console.input("Domain Name not binded. Press ENTER to continue...")
        except Exception as e:
            print(e)
            self.console.input("Failed. Press ENTER to continue...")

    def update_image_logic(self, service_info: dict, index: int):
        try:
            user_service = self._get_us_info_with_secret(service_info, index)
            while True:
                image_name = self.console.input('[bright_green]Please input the image name: ')
                if not is_image_name(image_name):
                    self.console.print('[bright_red]Invalid domain name. Please try again.')
                    continue
                if not self.us.check_image(image_name):
                    self.console.print('[bright_red]Please enter a publicly accessible image on Docker Hub: ')
                    continue
                break
            result = self.us.update_image(user_service["provider_host"], user_service["Secret"], image_name)
            self.console.input("Success. Press ENTER to continue...")
        except Exception as e:
            print(e)
            self.console.input("Failed. Press ENTER to continue...")

    def renew_user_service_logic(self, serviceList: list):
        try:
            renew_list = []
            renew_data = {}
            total_amount = 0
            renew_num = 0
            while True:
                result = self.console.input('[bright_green]Please input the service index or press enter to stop:').strip()
                if result == '':
                    break
                if not result.isdigit():
                    self.console.input('[bright_red]Please input a correct index number. Press ENTER to continue...')
                    continue
                index = int(result)
                if index < 0 or index > len(serviceList) - 1:
                    self.console.input('[bright_red]Index out of range. Press ENTER to continue...')
                    continue
                # Ask renewal duration
                duration = IntPrompt.ask('[bright_green]Please enter the renewal duration number: ')

                user_service_id = serviceList[index]['id']
                provider = serviceList[index]['provider']
                # calculate amount
                service_id = serviceList[index]['serviceID']
                service_info = self.service_info_cache[service_id]
                options = serviceList[index]['serviceOptions']
                price_set = self.ms.find_price_set(service_info['durationToPrice'], options)
                amount = calculate_amount(price_set, duration)

                # add renew service data for different provider
                if provider not in renew_data:
                    renew_data[provider] = {}
                renew_data[provider][user_service_id] = {
                    'userServiceID': user_service_id,
                    'duration': duration,
                    # use float amount for renew service api
                    'amount': float(amount)
                }
                total_amount += amount
                renew_num += 1

            if renew_num == 0:
                self.console.input('[bright_red]No input. Press ENTER to continue...')
                return

            # add to list to display amount in table
            for item in renew_data.values():
                renew_list.extend(list(item.values()))
            renew_list.append({'userServiceID':'Total', 'amount': total_amount})
            headers = [
                {"text": "User Service ID", "value": 'userServiceID'},
                {"text": "Duration", "value": "duration"},
                {"text": "Amount", "value": "amount"},
            ]
            w = self.ud.display_renew_amount(headers, renew_list)
            confirm = self.console.input(f'[bright_green]Are you sure to create the renewal order(default n) \[y/N]:').strip().lower()
            if confirm != 'y':
                return
            # create renew order for different providers
            for item in renew_data.values():
                order_brief = self.us.renew_usr_service(list(item.values()))
                self.md.display_order_brief(order_brief)
                confirm = self.console.input(f'[bright_green]Do you want to pay this renewal order directly?(default n) \[y/N]:').strip().lower()
                if confirm != 'y':
                    continue
                self.order_logic.pay_order(order_brief['id'])
        except Exception as e:
            print(f'Renew error. {e}')
            self.console.input("Renew error. Press enter and try again.")

    def refund_user_service_logic(self, serviceList: list):
        refund_num = 0
        refund_data = {}
        while True:
            result = self.console.input('[bright_green]Please input the service index or press enter to stop:').strip()
            if result == '':
                break
            if not result.isdigit():
                self.console.input('[bright_red]Please input a correct index number. Press ENTER to continue...')
                continue
            index = int(result)
            if index < 0 or index > len(serviceList) - 1:
                self.console.input('[bright_red]Index out of range. Press ENTER to continue...')
                continue

            service_id = serviceList[index]['serviceID']
            user_service_id = serviceList[index]['id']
            provider = serviceList[index]['provider']
            if not self.service_info_cache[service_id]['refundable']:
                self.console.input('[bright_red]This service is not refundable.Press ENTER to continue...')
                continue
            # save user services for different providers
            if provider not in refund_data:
                refund_data[provider] = []
            if user_service_id not in refund_data[provider]:
                refund_data[provider].append(user_service_id)
                refund_num += 1
        if refund_num == 0:
            self.console.input('[bright_red]No input. Press ENTER to continue...')
            return
        confirm = self.console.input(f'[bright_green]Are you sure to stop and refund the user services(default n) \[y/N]: ').strip().lower()
        if confirm != 'y':
            return
        # refund for different providers
        for refund_list in refund_data.values():
            self.us.refund_usr_service(refund_list)
        self.console.print('Success. Service is refunding. Please wait.')
        self.console.input("Press ENTER to continue...")
        

    def monitor_services(self, orders: list, index: int):
        status_list = []
        with Progress() as progress:
            task = progress.add_task("[green]Querying data...", total=len(orders) * 2, start=False)
            progress.start_task(task)
            try:
                for order in orders:
                    if order["serviceStatus"] != "ServiceRunning":
                        continue
                    user_service = self._get_us_info_with_secret(order, index)
                    progress.update(task, advance=1)
                    if user_service is None:
                        continue
                    status_data = self.us.service_status(user_service["provider_host"], user_service["Secret"])
                    progress.update(task, advance=1)
                    if status_data is None:
                        continue
                    item = {
                        "id": order["id"],
                        "region": order["serviceOptions"]["region"]
                    }
                    if "podStatus" in status_data and "containerStatuses" in status_data["podStatus"]:
                        container_status = status_data["podStatus"]["containerStatuses"][0]
                        item["image"] = container_status["image"]
                        state = list(container_status["state"].keys())
                        item["state"] = state[0]
                        item["restartCount"] = container_status["restartCount"]
                        item["hostIP"] = status_data["podStatus"]["hostIP"]

                    if not status_data:
                        item["state"] = "shutdown"

                    if "podSpec" in status_data and "containers" in status_data["podSpec"]:
                        # only one container in each pod
                        container = status_data["podSpec"]["containers"][0]
                        if "ports" in container:
                            item["ports"] = container["ports"]
                    if "domainName" in status_data and status_data["domainName"]:
                        item["domainName"] = status_data["domainName"]
                    status_list.append(item)
            except Exception as e:
                print(e)
                self.console.input("Press ENTER to continue...")
                return
        headers = [
            { "text": "User Service ID", "value": "id"},
            { "text": "Region", "value": "region"},
            { "text": "Image", "value": "image"},
            { "text": "Container Status", "value": "state"},
            # { "text": "Restart Count", "value": "restartCount"},
            { "text": "Host IP", "value": "hostIP"},
            { "text": "Port Spec", "value": "ports", "justify": "left"},
            { "text": "Domain Name", "value": "domainName"},
        ]
        title = '[bold bright_magenta] User Service Status'
        self.ud.display_service_status(title, headers, status_list)
        self.console.input("Press ENTER to continue...")

    def show_user_notice_page(self, status: str):
        cur = 1
        page_size = 10
        service_id = ''
        extra = self._get_page_choices(status)
        service_id = self.console.input('[bright_green]Please enter user service id for search (default All): ')
        while True:
            try:
                # only use v-kube-service provider for now
                # TODO: get all providers for the user
                provider_name = 'v-kube-service'
                provider_host = self.ms.get_provider_host(provider_name)
                display_result = self._construct_notice_service_page(cur, page_size, status, service_id, provider_host)
            except Exception as e:
                self.console.print(e)
                self.console.input("[bright_red]Failed to get user notice info. Press ENTER to continue...")
                break
            w = self.ud.display_notice_table(status, display_result['list'])
            has_next = len(display_result['list']) >= page_size and display_result['pagination']['total']/page_size > cur
            choice = get_table_choice(self.console, w, has_next, extra=extra)
            if choice == 'p' and cur > 1:
                cur -= 1
            elif choice == 'n' and has_next:
                cur += 1
            elif choice == 'e':
                break

    def show_user_service_page(self, status: str):
        cur = 1
        page_size = 10
        service_id = ''
        extra = self._get_page_choices(status)
        while True:
            try:
                display_result = self._get_user_service_data(cur, page_size, status)
            except Exception as e:
                self.console.print(e)
                self.console.input("[bright_red]Failed to get userservice info. Press ENTER to continue...")
                break
            self.get_service_info_data(display_result['list'], self.nonce)
            w = self.ud.display_service_table(status, display_result['list'], self.service_info_cache)
            has_next = len(display_result['list']) >= page_size and display_result['pagination']['total']/page_size > cur
            service_list = display_result['list']
            choice = get_table_choice(self.console, w, has_next, extra=extra)
            if choice == 'p' and cur > 1:
                cur -= 1
            elif choice == 'n' and has_next:
                cur += 1
            elif choice == 'e':
                break
            elif choice == 'd':
                idx = self.get_target_index(service_list)
                us_info = service_list[idx]
                self.ud.show_user_service_detail(us_info)
            elif status == 'ServiceRunning' and choice == 'b':
                idx = self.get_target_index(service_list)
                self.bind_domain_logic(service_list[idx], self.nonce)
            elif status == 'ServiceRunning' and choice == 'u':
                idx = self.get_target_index(service_list)
                self.unbind_domain_logic(service_list[idx], self.nonce)
            elif (status == 'ServicePending' or status == 'ServiceRunning') and choice == 'a':
                idx = self.get_target_index(service_list)
                self.access_provider_api_logic(service_list[idx], self.nonce)
            elif status == 'ServicePending' and choice == 's':
                idx = self.get_target_index(service_list)
                us_info = service_list[idx]
                self.start_user_service_api_logic(us_info, self.nonce)
            elif self.interface_index == 1 and choice == 't':
                idx = self.get_target_index(service_list)
                us_info = service_list[idx]
                self.tail_service_log_logic(us_info, self.nonce)
            elif self.interface_index == 1 and choice == 'r':
                idx = self.get_target_index(service_list)
                us_info = service_list[idx]
                self.restart_user_service_logic(us_info, self.nonce)
            elif self.interface_index == 1 and choice == 'm':
                temp_list = []
                idx = self.console.input('[bright_green]Please enter the user service index (default All): ').strip()
                if idx != '':
                    temp_list.append(service_list[int(idx)])
                else:
                    temp_list = service_list
                if len(temp_list) == 0:
                    temp_list = display_result["list"]
                self.monitor_services(temp_list, self.nonce)
            elif self.interface_index == 1 and choice == 'g':
                self.renew_user_service_logic(display_result['list'])
            elif self.interface_index == 1 and choice == 'h':
                idx = self.get_target_index(service_list)
                self.update_image_logic(service_list[idx], self.nonce)
            elif (self.interface_index == 1 or self.interface_index == 2) and choice == 'f':
                self.refund_user_service_logic(display_result['list'])

    def validate_user_service(self, result: dict, user_service_id: str) -> (dict, bool):
        found = False
        for u in result['list']:
            if u['id'] == user_service_id:
                found = True
                return u, found
        if not found:
            self.console.input("[bright_red]User Service ID Not Found.[/] Press ENTER to continue...")
            return None, found

    def get_target_index(self, data_list: dict):
        return get_table_index(data_list, '[bright_green]Please enter the User Service INDEX')

    def get_service_info_data(self, service_list: list, index: int = 0):
        for service in service_list:
            service_id = service['serviceID']
            if service_id not in self.service_info_cache:
                service_info = self.ms.get_service_info(service_id)
                self.service_info_cache[service_id] = service_info

    def _construct_notice_service_page(self, cur_page: int, page_size: int, status: str, service_id: str = '', provider_host: str = ''):
        display_result = self.us.get_user_notice_info(
            current=cur_page,
            page_size=page_size,
            service_id=service_id,
            provider_host=provider_host
        )
        return display_result

    def _get_user_service_data(self, cur_page: int, page_size: int, status: str):
        result = self.us.get_user_service_data(
            current=cur_page,
            page_size=page_size,
            statuses=[status]
        )
        return result

    def _get_page_choices(self, status: str):
        extra = {}
        if status == 'ServiceRunning':
            extra = {'d': '[D]User Service Detail', 'a': '[A]User Service API Access', 't': '[T]Tail Service Log', 'b': '[B]Bind domain name', 'u': '[U]Unbind domain name', 'm': '[M]Service Status', 'r': '[R]Restart Service', 'f': '[F]Refund Service', 'g': '[G]Renew Service', 'h': '[H]Update Image'}
        elif status == 'ServicePending':
            extra = {'d': '[D]User Service Detail', 'a': '[A]User Service API Access', 's': '[S]Start a service', 'f': '[F]Refund Service'}
        return extra

    def _get_us_info_with_secret(self, order_info: dict, wallet_index: int) -> dict:
        # get decryption private key
        try:
            private_key = self.state.get_current_account().privateKey
            provider_host = self.ms.get_provider_host(order_info['provider'])
            info = self.us.get_user_service_info(provider_host, order_info["id"])
            info["provider_host"] = provider_host
            plain_txt_magic = decrypt_message(private_key, info["magic"])
            magic_dict = json.loads(plain_txt_magic)
            return { **magic_dict, **info }
        except Exception as err:
            print(err)
            return None

    def read_stream(self, data):
        line = data.decode("utf-8")
        if line.startswith("data:"):
            line = line[5:]
        if line != "\"logger - Client disconnected.\"":
            self.console.print(line)
