#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.

import time

from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support.select import Select
from selenium.webdriver.support import expected_conditions as EC

from util import (
    BSX_0_PORT,
    BSX_1_PORT,
    click_option,
    get_driver,
)
from tests.basicswap.util import read_json_api

base_url = "http://localhost"


def recover_chain_b_lock_tx(driver, offer_data, offerer_port, bidder_port):
    print("Test case: {} -> {}".format(offer_data["coin_from"], offer_data["coin_to"]))
    offerer_url = f"{base_url}:{offerer_port}"
    bidder_url = f"{base_url}:{bidder_port}"

    rv = read_json_api(offerer_port, "offers/new", offer_data)
    offer0_id = rv["offer_id"]

    for i in range(10):
        rv = read_json_api(bidder_port, f"offers/{offer0_id}")
        if len(rv) > 0:
            break
        print("Bidder: Waiting for offer")
        time.sleep(1)

    bid_data = {"offer_id": offer0_id, "amount_from": 1.0}
    rv = read_json_api(bidder_port, "bids/new", bid_data)
    bid0_id = rv["bid_id"]

    bid_state = None
    for i in range(10):
        rv = read_json_api(offerer_port, f"bids/{bid0_id}")
        if "error" not in rv:
            bid_state = rv["bid_state"]
            if bid_state == "Received":
                break
        print("Offerer: Waiting for bid")
        time.sleep(2)
    assert bid_state == "Received"

    # Set BID_STOP_AFTER_COIN_B_LOCK (13) debugind
    rv = read_json_api(offerer_port, f"bids/{bid0_id}", {"debugind": 13})
    assert "error" not in rv

    # Accept bid
    rv = read_json_api(offerer_port, f"bids/{bid0_id}", {"accept": 1})
    assert "error" not in rv

    for i in range(100):
        rv = read_json_api(bidder_port, f"bids/{bid0_id}")
        bid_state = rv["bid_state"]
        if bid_state == "Scriptless coin locked":
            break
        print("Bidder: Waiting for state")
        time.sleep(5)
    assert bid_state == "Scriptless coin locked"

    for i in range(100):
        rv = read_json_api(offerer_port, f"bids/{bid0_id}")
        bid_state = rv["bid_state"]
        if bid_state == "Stalled (debug)":
            break
        print("Offerer: Waiting for state")
        time.sleep(5)
    assert bid_state == "Stalled (debug)"

    # Show bid state history
    rv = read_json_api(offerer_port, f"bids/{bid0_id}/states")
    assert len(rv) > 1

    url = f"{bidder_url}/bid/{bid0_id}"
    driver.get(url)
    btn_more_info = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "show_txns"))
    )
    btn_more_info.click()
    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.NAME, "hide_txns")))
    bidder_localkeyhalf = driver.find_element(By.ID, "localkeyhalf").text
    print("Bidder keyhalf", bidder_localkeyhalf)
    try:
        driver.find_element(By.ID, "remotekeyhalf")
    except Exception:
        pass
    else:
        raise ValueError("Nodes should not have remotekeyhalves yet.")

    url = f"{offerer_url}/bid/{bid0_id}"
    driver.get(url)
    btn_more_info = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "show_txns"))
    )
    btn_more_info.click()
    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.NAME, "hide_txns")))
    offerer_localkeyhalf = driver.find_element(By.ID, "localkeyhalf").text
    print("Offerer keyhalf", offerer_localkeyhalf)

    print("Trying with the local key in place of remote")
    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    btn_edit.click()
    btn_submit = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid_submit"))
    )
    kbs_other = driver.find_element(By.ID, "kbs_other")
    kbs_other.send_keys(offerer_localkeyhalf)
    btn_submit.click()

    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    elements = driver.find_elements(By.CLASS_NAME, "error_msg")
    expect_err_msg: str = "Provided key matches local key"
    assert any(expect_err_msg in el.text for el in elements)
    print("Found expected error: " + expect_err_msg)

    print("Trying with incorrect key")
    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    btn_edit.click()
    btn_submit = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid_submit"))
    )
    kbs_other = driver.find_element(By.ID, "kbs_other")
    last_byte = bidder_localkeyhalf[-2:]
    invalid_byte = "01" if last_byte == "00" else "00"
    kbs_other.send_keys(bidder_localkeyhalf[:-2] + invalid_byte)
    btn_submit.click()

    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    elements = driver.find_elements(By.CLASS_NAME, "error_msg")
    expect_err_msg: str = "Summed key does not match expected wallet"
    assert any(expect_err_msg in el.text for el in elements)
    print("Found expected error: " + expect_err_msg)

    print("Trying with correct key")
    btn_edit.click()
    btn_submit = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid_submit"))
    )

    lock_tx_b_depth = -1
    for i in range(100):
        # Check the non-stalled node
        rv = read_json_api(bidder_port, f"bids/{bid0_id}", {"show_extra": True})
        for tx in rv["txns"]:
            if tx["type"] == "Chain B Lock" and tx["confirms"] is not None:
                lock_tx_b_depth = tx["confirms"]
                break
        if lock_tx_b_depth >= 10:
            break
        print(f"Waiting for lock tx B depth, have {lock_tx_b_depth}")
        time.sleep(2)

    kbs_other = driver.find_element(By.ID, "kbs_other")
    kbs_other.send_keys(bidder_localkeyhalf)
    btn_submit.click()
    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    elements = driver.find_elements(By.CLASS_NAME, "infomsg")
    expect_msg: str = "Bid edited"
    assert any(expect_msg in el.text for el in elements)
    print("Found expected message: " + expect_msg)

    print(
        "Trying with nodes reversed (should fail as already spent)"
    )  # But should sum to the expected wallet key
    url = f"{bidder_url}/bid/{bid0_id}"
    driver.get(url)
    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    btn_edit.click()
    btn_submit = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid_submit"))
    )

    driver.get(url)
    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    btn_edit.click()
    btn_submit = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid_submit"))
    )

    kbs_other = driver.find_element(By.ID, "kbs_other")
    kbs_other.send_keys(offerer_localkeyhalf)
    btn_submit.click()
    btn_edit = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "edit_bid"))
    )
    # In log: "Balance is too low, checking for existing spend"
    # Should error here, but the code can't tell where the tx was sent, and treats any existing send as correct.
    elements = driver.find_elements(By.CLASS_NAME, "infomsg")
    expect_msg: str = "Bid edited"
    assert any(expect_msg in el.text for el in elements)


def enable_debug_ui(driver):
    for port in (BSX_0_PORT, BSX_1_PORT):
        url = f"{base_url}:{port}/settings"
        driver.get(url)
        driver.find_element(By.ID, "general-tab").click()

        btn_apply_general = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.NAME, "apply_general"))
        )

        el = driver.find_element(By.NAME, "debugmode")
        selected_option = Select(el).first_selected_option
        if selected_option.text != "True":
            click_option(el, "True")

        el = driver.find_element(By.NAME, "debugui")
        selected_option = Select(el).first_selected_option
        if selected_option.text != "True":
            click_option(el, "True")

        btn_apply_general.click()


def run_tests():
    driver = get_driver()
    try:
        enable_debug_ui(driver)

        offer_data = {
            "coin_from": "BTC",
            "coin_to": "XMR",
            "amt_from": 1.0,
            "amt_to": 2.0,
            "lockhrs": 24,
        }
        recover_chain_b_lock_tx(driver, offer_data, BSX_0_PORT, BSX_1_PORT)

        offer_data = {
            "coin_from": "XMR",
            "coin_to": "BTC",
            "amt_from": 1.0,
            "amt_to": 2.0,
            "lockhrs": 24,
        }
        recover_chain_b_lock_tx(driver, offer_data, BSX_1_PORT, BSX_0_PORT)

        print("Test Passed!")
    finally:
        driver.close()


if __name__ == "__main__":
    run_tests()
