import asyncio
import json
import logging
from random import choice
from sys import argv
from typing import List

import requests
import urllib3
from fake_useragent import UserAgent

from exceptions import AccountBannedException, PostDeletedException

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class SendPost:
    post_url = "https://bbs.hupu.com/post.php?action=reply"
    user_agent = UserAgent().random
    session = requests.Session()
    signature = '本回复由虎扑非官方机器人自动发送。如果你对这个回复有什么问题或建议，请回复或私信。'

    def __init__(self, sub_name: str, queries: List[str], reply_type: str):
        self.sub_name = sub_name
        self.queries = queries
        self.reply_type = reply_type
        self.cookie = self._get_cookie()
        self.headers = self._get_headers()
        self.deleted_post_ids = []
        self.banned_sub_ids = []
        self.do_not_reply_users = self.get_do_not_reply_users()
        self.previously_replied_floors = self.get_previously_replied_floors()
        self.recently_replied_floors = []

    @staticmethod
    def _get_cookie():
        with open("cookie.txt", "r", encoding="utf-8") as f:
            return f.read()

    def _get_headers(self):
        return {
            "content-type": "application/x-www-form-urlencoded",
            "user-agent": self.user_agent,
            "cookie": self.cookie,
            "charset": "utf-8",
        }

    def get_do_not_reply_users(self):
        with open("data/global/do_not_reply.json", encoding="utf-8") as f:
            return json.loads(f.read())["users"]

    def get_previously_replied_floors(self):
        try:
            with open(f"data/global/replied_floors.json", encoding="utf-8") as f:
                return json.loads(f.read())
        except (FileNotFoundError, ValueError):
            return []

    def get_stats_for_pairs(self, a, b):
        with open(f"data/{self.sub_name}/stats.json", encoding="utf-8") as f:
            stats = json.loads(f.read())
        return {
            stat_name: {a: stat[a], b: stat[b]} for stat_name, stat in stats.items()
        }

    @staticmethod
    def _get_comparison_pairs(query, content):
        content = content.replace(f"#{query}#", "").strip()
        a, b = content.split("vs", maxsplit=1)
        a, b = a.split(" ")[-1], b.split(" ")[0]
        return a, b

    @staticmethod
    def format_stats(stats):
        string_builder = []
        for stat_name, stat in stats.items():
            string_builder.append(f"{stat_name}:\n")
            for item, value in stat.items():
                string_builder.append(f"\t{item}: {value}\n")
        return "".join(string_builder)

    @staticmethod
    def format_newlines(input: str):
        return input.replace("\n", "
")

    def get_comparison_reply_content(self, query: str, quote_content: str) -> str:
        try:
            pairs = self._get_comparison_pairs(query, quote_content)
            result = self.format_stats(self.get_stats_for_pairs(*pairs))
        except ValueError:
            result = "comparison not found"
        return result

    def get_keyword_reply_content(self, query: str) -> str:
        try:
            with open(
                f"data/{self.sub_name}/input/keyword_reply.json", encoding="utf-8"
            ) as f:
                keyword_mapper = json.loads(f.read())
                result = keyword_mapper[query]
                if (
                    type(result) == str
                    and result.startswith("%")
                    and result.endswith("%")
                ):
                    result = keyword_mapper[result.strip("%")]
                result = choice(result)
        except KeyError:
            result = "keyword not found"
        return result

    def get_licking_dog_reply_content(self, query: str) -> str:
        try:
            return self.session.get(
                "https://api.ixiaowai.cn/tgrj/index.php", verify=False
            ).text
        except requests.exceptions.RequestException:
            return "机器人出问题了，再试试吧？"

    def _get_reply_content(self, reply_type, **kw):
        reply_type_function_map = {
            "keyword": self.get_keyword_reply_content,
            "comparison": self.get_comparison_reply_content,
            "licking_dog": self.get_licking_dog_reply_content,
        }
        reply_type_args_map = {
            "keyword": ["query"],
            "comparison": ["query", "quote_content"],
            "licking_dog": ["query"],
        }
        reply_type_function = reply_type_function_map[reply_type]
        reply_type_kwargs: List = {
            arg: kw[arg] for arg in reply_type_args_map[reply_type]
        }
        return reply_type_function(**reply_type_kwargs)

    def get_replies_metadata(self, queries, reply_type):
        with open(f"data/{self.sub_name}/floors.json", encoding="utf-8") as f:
            floors_to_reply = json.loads(f.read())
        reply_metadata = []
        for post_id, post in floors_to_reply.items():
            sub_id = post["meta"]["sub_id"]
            floors = post["floors"]
            for floor in floors.values():
                if (
                    floor["username"] in self.do_not_reply_users
                    or {"post_id": post_id, "floor_id": floor["floor_id"]}
                    in self.previously_replied_floors
                ):
                    continue
                floor_id = floor["floor_id"]
                quote_content = floor["content"]
                for query in queries:
                    if query in quote_content:
                        content = self._get_reply_content(
                            reply_type,
                            query=query,
                            quote_content=quote_content,
                        )
                        reply_metadata.append(
                            {
                                "quote_floor_id": floor_id,
                                "content": f"{content}\n\n{self.signature}",
                                "sub_id": sub_id,
                                "post_id": post_id,
                            }
                        )
        return reply_metadata

    def get_all_replies(self):
        replies = self.get_replies_metadata(self.queries, self.reply_type)
        with open(f"data/{self.sub_name}/replies.json", "w", encoding="utf-8") as f:
            f.write(json.dumps(replies, indent=4, ensure_ascii=False))
        return replies

    def mark_replied_floors(self):
        with open(f"data/global/replied_floors.json", "w", encoding="utf-8") as f:
            replied_floors = self.previously_replied_floors + [
                floor
                for floor in self.recently_replied_floors
                if floor not in self.previously_replied_floors
            ]
            f.write(json.dumps(replied_floors, indent=4, ensure_ascii=False))

    def test_account_banned(self, sub_id, post_id):
        response = requests.get(
            f"https://bbs.hupu.com/post.php?fid={sub_id}&tid={post_id}",
            headers=self.headers,
            timeout=10,
        )
        if "您在该板块封禁中" in response.text:
            raise AccountBannedException("Account banned")

    async def try_replying(self, url, headers, payload, times=1):
        try:
            response = requests.post(
                url,
                headers=headers,
                data=payload,
                timeout=10,
            )
            if "页面不存在" in response.text:
                raise PostDeletedException()
            if "出错" in response.text:
                # doesn't actually post something
                self.test_account_banned(sub_id=payload["fid"], post_id=payload["tid"])
                raise requests.exceptions.HTTPError("嗯，出错了。")
            response.raise_for_status()
        except (requests.exceptions.HTTPError, requests.exceptions.ReadTimeout) as e:
            logging.info(e)
            await asyncio.sleep(3)
            if times < 3:
                return asyncio.create_task(
                    self.try_replying(url, headers, payload, times=times + 1)
                )
            else:
                return -1
        else:
            replied_floor = {
                "post_id": payload["tid"],
                "floor_id": payload["quotepid"],
            }
            print(f"Success! {replied_floor}")
            logging.info(f"Success! {replied_floor}")
            self.recently_replied_floors.append(replied_floor)
            return response

    async def send_reply(self, metadata):
        post_id = metadata["post_id"]
        sub_id = metadata["sub_id"]
        if sub_id in self.banned_sub_ids or post_id in self.deleted_post_ids:
            return -1
        quote_floor_id = metadata["quote_floor_id"]
        content = metadata["content"]
        payload = {
            "atc_content": self.format_newlines(content),
            "step": 2,
            "action": "reply",
            "fid": sub_id,
            "tid": post_id,
            "atc_html": 1,
        }
        if quote_floor_id != "tpc":  # it's not OP who sent it.
            payload["quotepid"] = quote_floor_id

        try:
            return await self.try_replying(self.post_url, self.headers, payload)
        except PostDeletedException:
            logging.error(f"Deleted:{post_id}")
            self.deleted_post_ids.append(post_id)
        except AccountBannedException:
            logging.error(f"Banned:{sub_id}")
            self.banned_sub_ids.append(sub_id)

    async def send_all_replies(self, debug=False):
        replies = self.get_all_replies()
        if not debug:
            tasks = []
            for reply in replies:
                tasks.append(asyncio.create_task(self.send_reply(reply)))
            for task in tasks:
                await task
            self.mark_replied_floors()

async def send_posts(sub_name, reply_type, debug):
    if reply_type == "keyword":
        with open(f"data/{sub_name}/input/keyword_reply.json", encoding="utf-8") as f:
            queries = json.loads(f.read()).keys()
    elif reply_type == "licking_dog":
        queries = ["#舔狗日记#"]
    send_post = SendPost(sub_name, queries=queries, reply_type=reply_type)
    send_post.get_all_replies()
    task = asyncio.create_task(send_post.send_all_replies(debug))
    await task

if __name__ == "__main__":
    if len(argv) == 1:
        sub_name = "bxj"
    else:
        sub_name = argv[1]
    asyncio.run(send_posts(sub_name, reply_type="licking_dog", debug=True))


#!python3
import os, sys, re, csv, sqlite3, string
from datetime import datetime, timedelta
from time import strptime, strftime
from itertools import chain

maxlinecount = 16000000
linecount = 0
filecount = 0

# Sample LIne
# 192.168.117.1 - - [20/Jun/2013:01:38:40 -0400] "GET /Dkrh8Ycb.htm HTTP/1.0" 404 210 "-" "Mozilla/5.00"

header = ["DATE_TIME","SRC_FILE","SRC_IP","REQ_TYPE","URI","HTTP_STATUS_CODE","BYTES","REFFER","USER_AGENT_STRING","RAW_LINE"]

def parsefile(fname,ofile):
	# REGEX Patterns
	http_bytes_useragentstring_pattern = re.compile("\"([A-Z]{1,10}|[a-z]{1,10}) (.{1,10000})\" (\d{1,1000}) (\d{1,1000}|-) \"(-|.{1,500})\" \"(.{1,500})\"")
	ip_address_pattern = re.compile('(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)')
	datetime_pattern = re.compile('\[([0-9]{2}\/[A-Z][a-z]{2}\/[0-9]{4}\:[0-9]{2}\:[0-9]{2}:[0-9]{2}) ([\+,\-][0-9]{4})\]')

	tmp_lcount = 0
	for line in open(fname,'r'):
		if line[0] != "#":
			output_line = []
			# Prefill the value of each field before starting loop
			finaldate = "-"
			srcip = "-"
			URL = "-"
			HTTPSTATUSCODE = "-"
			BYTES1 = "-"
			BYTES2 = "-"
			REFFER = "-"
			port = "-"
			useragentstring = "-"

			# # Check for NULL line
			# if line == " ":
			# 	continue

			# DATE
			rawdate = datetime_pattern.search(line)
			if rawdate:
				datetime2 = rawdate.group(1)
				offset = rawdate.group(2)
				finaldate = normalize_datetime(datetime2,offset)
			output_line.append(str(finaldate))

			#FILENAME
			try:
				output_line.append(fname)
			except:
				print("ERROR: Parsing FILENAME error on line")
				#print(line)
				error_file.write(line)
				continue

			#SRCIP and SRC_PORT
			srcip_atmp = ip_address_pattern.search(line)
			#srcinfo = status_type_url_port_pattern.search(line)

			if srcip_atmp:
				srcip = srcip_atmp.group().split(":")[0]
			output_line.append(srcip)

			#HTTPSTATUSCODE and BYTES
			bytesinfo = http_bytes_useragentstring_pattern.search(line)
			if bytesinfo:
				REQ_TYPE = bytesinfo.group(1)
				URL = bytesinfo.group(2)
				BYTES1 = bytesinfo.group(4)
				HTTPSTATUSCODE = bytesinfo.group(3)
				REFFER = bytesinfo.group(5)
				useragentstring = bytesinfo.group(6)

			output_line.append(REQ_TYPE)
			output_line.append(URL)
			output_line.append(HTTPSTATUSCODE)
			output_line.append(BYTES1)
			output_line.append(REFFER)
			output_line.append(useragentstring)

			# RAW_LINE
			output_line.append(line.strip())

			ofile.write('\t'.join(output_line)+"\n")
			tmp_lcount += 1
			#print(tmp_lcount)
	return tmp_lcount

def normalize_datetime(rawdate,offset):
	# RAW Date: 28/Feb/2015:00:05:03
	# RAW Offset: -0800
	tmpdate = datetime.strptime(rawdate,'%d/%b/%Y:%H:%M:%S')
	if offset[0:1] == "-":
		finaldate = tmpdate + timedelta(hours=int(offset[1:3]),minutes=int(offset[3:5]))
	if offset[0:1] == "+":
		finaldate = tmpdate - timedelta(hours=int(offset[1:3]),minutes=int(offset[3:5]))
	return finaldate

def getfilenames(fext):
	listoffiles = []
	for root, dirs, files in os.walk(os.curdir):
		for file in files:
			if file.endswith(fext):
				listoffiles.append(os.path.join(root, file))
	return listoffiles

def new_outfile(filecount):
	ofilename = "all_output_file_final_"+ str(filecount) +".log"
	print(ofilename)
	return open(ofilename,'w')

# Start the error file
error_file = open("unparsed_lines.log",'w')

# Start the first file and write the header
mofile = new_outfile(filecount)
mofile.write('\t'.join(header)+"\n")

# Get the list of filenames from the directory ending with the string provided to the function
lof = getfilenames(sys.argv[1])

# Cycle through list of filenames and parse the data
for fname in lof:
	linecount += parsefile(fname, mofile)
	if linecount > maxlinecount:
		mofile.close()
		filecount += 1
		mofile = new_outfile(filecount)
		mofile.write('\t'.join(header)+"\n")
		print('\t'.join(header))
		linecount = 0

# Close the final file
mofile.close()

# Close the error file
error_file.close()


import json
from django.http import JsonResponse

def calculate_similarity(user1, user2):
    # age similarity
    age_difference = abs(user1['age'] - user2['age'])
    age_similarity = 1 / (age_difference + 1)  # handling divided by zero

    # interest similarity (common interest)
    shared_interests = set(user1['interests'].keys()) & set(user2['interests'].keys())
    interest_similarity = sum(
        user1['interests'][interest] * user2['interests'][interest]
        for interest in shared_interests
    )

    # calculating score after combining
    overall_similarity = (age_similarity + interest_similarity) / 2

    # print(overall_similarity)

    return overall_similarity

def recommend_friends(user_data, all_users_data):
    # similarity score for each user
    user_scores = []
    for user in all_users_data:
        if user['id'] != user_data['id']:
            similarity_score = calculate_similarity(user_data, user)
            user_scores.append((user, similarity_score))

    # users by similarity score in descending order
    user_scores.sort(key=lambda x: x[1], reverse=True)

    # Return the sorted list of users
    return [user_data for user_data, _ in user_scores]

# returning json file
def get_suggested_friends(request, user_id):
    # print(int(user_id))
    # loading json data
    with open('friends/users.json', 'r') as json_file:
        data = json.load(json_file)

    # Get the user data for the specified user_id

    # print(data['users'][0])

    data = data['users']
    # user_id = 5 # for testing
    user_data = None
    for user in data:
        if user['id'] == user_id:
            user_data = user
            break

    if not user_data:
        return JsonResponse({'error': 'User not found'}, status=404)

    # algorithm
    suggested_friends = recommend_friends(user_data, data)

    # top 5 suggested friends
    top_5_suggested_friends = suggested_friends[:5]

    context = {
        'user_data': user_data,
        'user_id': user_id,
        'suggested_friends': top_5_suggested_friends,
    }

    return JsonResponse(context)


#!/usr/bin/env python

from ctypes import *
import random
import os
import cv2
import time
import darknet
import argparse
import rospy
from threading import Thread, enumerate
from queue import Queue

import rospkg

from cv_bridge import CvBridge, CvBridgeError
from robosub_msgs.msg import Detection, DetectionArray
from sensor_msgs.msg import Image
from std_msgs.msg import String
from rospy.numpy_msg import numpy_msg

publish_img = True

width = None
height = None
network = None
class_names = None
class_colors = None
thresh = None

save_imgs = True
camera_selection = "front"
bridge = None

detections_pub = None
img_pub = None

frame_queue = None
darknet_image_queue = None
detections_queue = None
fps_queue = None

ext_output = None
dont_show = True

def load_args():
    '''
    Initializes the darknet.
    '''
    # use ros param
    global thresh

    rospack = rospkg.RosPack()
    path_to_darknet = rospack.get_path('darknet_v4')

    configPath = path_to_darknet + "/src/cfg/yolov4-tiny.cfg"
    weightPath = path_to_darknet + "/src/weights/yolov4-tiny.weights"
    dataFile = path_to_darknet+ "/src/cfg/coco.data"

    thresh = .25 #remove detections with confidence below this value

    return configPath, weightPath, dataFile

def check_arguments_errors(configPath, weightPath, dataFile):

    if not os.path.exists(configPath):
        raise(ValueError("Invalid config path {}".format(os.path.abspath(configPath))))
    if not os.path.exists(weightPath):
        raise(ValueError("Invalid weight path {}".format(os.path.abspath(weightPath))))
    if not os.path.exists(dataFile):
        raise(ValueError("Invalid data file path {}".format(os.path.abspath(dataFile))))

def load_network(configPath, weightPath, dataFile):
    global width, height, network, class_names, class_colors

    network, class_names, class_colors = darknet.load_network(
            configPath,
            dataFile,
            weightPath,
            batch_size=1
        )

    # Create one with image we reuse for each detect
    width = darknet.network_width(network)
    height = darknet.network_height(network)
    darknet_image = darknet.make_image(width, height, 3)

    return darknet_image

def set_saved_video(input_video, output_video, size):
    fourcc = cv2.VideoWriter_fourcc(*"MJPG")
    fps = int(input_video.get(cv2.CAP_PROP_FPS))
    video = cv2.VideoWriter(output_video, fourcc, fps, size)
    return video

def video_capture(frame):
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame_resized = cv2.resize(frame_rgb, (width, height),
                               interpolation=cv2.INTER_LINEAR)

    darknet.copy_image_from_bytes(darknet_image, frame_resized.tobytes())
    return frame_resized, darknet_image

def inference(darknet_image):

    prev_time = time.time()
    detections = darknet.detect_image(network, class_names, darknet_image, thresh=thresh)
    fps = int(1/(time.time() - prev_time))

    print("FPS: {}".format(fps))
    darknet.print_detections(detections, ext_output)
    detections_pub.publish(detections)

    return detections, fps

def drawing(frame_resized, detections):
    random.seed(3)  # deterministic bbox colors

    if frame_resized is not None:
        image = darknet.draw_boxes(detections, frame_resized, class_colors)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        if not dont_show:
            cv2.imshow('Inference', image)

    image_pub.publish(bridge.cv2_to_imgmsg(image, "bgr8"))
    #cv2.imshow('',image)

    if detections != [] and save_imgs:
        save_image(image)

def save_image(im0):
    '''
    Saves images of the detections
    '''

    rospack = rospkg.RosPack()
    path_to_darknet = rospack.get_path('darknet_v4')

    save_path = path_to_darknet + '/src/saved_img/' + str(time.time()) + ".jpg"
    cv2.imwrite(save_path, im0)

def selection_callback(data):
    '''
    Sets camera selection to front when data='front' and bottom when data='bottom'.
    '''
    global camera_selection
    camera_selection = data.data

def front_callback(data):
    '''
    Calls run_darknet using images from front camera.
    '''
    if camera_selection == "front":
        run_darknet(data)
    else:
        pass

def bottom_callback(data):
    '''
    Calls run_darknet using images from bottom camera.
    '''
    if camera_selection == "bottom":
        run_darknet(data)
    else:
        pass

def run_darknet(data):
    '''
    Runs all darknet functions
    '''

    prev_time = time.time()

    frame = bridge.imgmsg_to_cv2(data)

    frame_resized, darknet_image = video_capture(frame)
    detections, fps = inference(darknet_image)
    drawing(frame_resized, detections)

    print(1/(time.time()-prev_time))

def YOLO():
    '''
    Main YOLO loop
    '''
    global darknet_image, bridge, detections_pub, image_pub, frame_queue, darknet_image_queue, detections_queue, fps_queue

    configPath, weightPath, dataFile = load_args()
    check_arguments_errors(configPath, weightPath, dataFile)
    darknet_image = load_network(configPath, weightPath, dataFile)

    detections_pub = rospy.Publisher('darknetv4', DetectionArray, queue_size=10)
    image_pub = rospy.Publisher("darknet_image", Image, queue_size=10)

    rospy.init_node('Darknetv4Node', anonymous=True)

    rospy.Subscriber('/change_camera', String, selection_callback)

    rospy.Subscriber('/camera/left/image_raw', Image, front_callback)
    #rospy.Subscriber('/usb_cam/image_raw', Image, front_callback)
    rospy.Subscriber('/camera/bottom/left/image_raw', Image, bottom_callback)

    bridge = CvBridge()

    print("Starting the YOLO loop...")

    rospy.spin()

if __name__ == '__main__':

    YOLO()


from flask import Blueprint
from app import db
from flask import render_template
from models import Post
from .forms import PostForm
from flask import request
from flask import redirect
from flask import url_for

posts = Blueprint('posts', __name__, template_folder='templates')

@posts.route('/create', methods=['POST',  'GET'])
def create_post():

    if request.method == 'POST':
        title = request.form['title']
        content = request.form['content']

        try:
            post = Post(title=title, content=content)
            db.session.add(post)
            db.session.commit()
        except:
            print('Ошибка')

        return redirect( url_for('posts.index'))
    form = PostForm()
    return render_template('posts/create_post.html', form=form)

@posts.route('/')
def index():
    post_model = Post(db.get_connection())
    return render_template('posts/index.html', posts=post_model)


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

"""This module checks whether the broker responded appropriately to the
auto-scheduler's cue. If it has not, a message(s) is logged with
`severity = 'CRITICAL'` which triggers a GCP alerting policy.
"""

import base64
import os

from google.cloud import logging
from googleapiclient import discovery

PROJECT_ID = os.getenv('GCP_PROJECT')
TESTID = os.getenv('TESTID')
SURVEY = os.getenv('SURVEY')
ZONE = os.getenv('ZONE')

compute_service = discovery.build('compute', 'v1')

# connect to the logger
logging_client = logging.Client()
log_name = 'check-cue-response-cloudfnc'  # same log for all broker instances
logger = logging_client.logger(log_name)

# GCP resources used in this module
consumer = f'{SURVEY}-consumer'  # vm
night_conductor = f'{SURVEY}-night-conductor'  # vm
if TESTID != "False":
    consumer = f'{consumer}-{TESTID}'
    night_conductor = f'{night_conductor}-{TESTID}'

def run(msg, context) -> None:
    """ Entry point for the Cloud Function
    Args:
        msg (dict): Pub/Sub message.

                    `data` field (bytes) contains the message data. It is
                    expected to be one of ['START', 'END'].

                    `attributes` field (dict) contains the message's custom
                    attributes.
        context (google.cloud.functions.Context): The Cloud Functions event
            metadata. The `event_id` field contains the Pub/Sub message ID. The
            `timestamp` field contains the publish time.
    """
    cue = base64.b64decode(msg['data']).decode('utf-8')  # 'START' or 'END'
    attrs = msg['attributes']  # dict

    continue_checks = check_cue_value(cue)  # check that cue is an expected value
    if continue_checks:  # do the checks
        check_cue_response(cue, attrs)

def check_cue_value(cue):
    # check that the cue is an expected value and log result
    expected_values = ['START', 'END']

    if cue in expected_values:
        continue_checks = True
        msg = (f'Broker instance with keywords [{SURVEY},{TESTID}] received '
               f'cue = {cue}. Giving the broker time to '
               'respond, then will check its response to the cue.'
               )
        severity = 'INFO'
    else:
        continue_checks = False
        msg = (f'Broker received cue = {cue}, which is an unexpected value. '
               'The broker is not expected to respond. No further checks will be done.')
        severity = 'CRITICAL'
    logger.log_text(msg, severity=severity)

    return continue_checks

def check_cue_response(cue, attrs):
    """Check that the broker components responded appropriately to the cue.
    """
    # check that consumer has started or stopped, as appropriate
    check_consumer(cue, attrs)

    # check that night conductor has started
    if cue == "END":
        _ = check_night_conductor()

def check_night_conductor():
    # night-conductor should start in response to either cue
    request_kwargs = {
        'project': PROJECT_ID,
        'zone': ZONE,
        'instance': night_conductor
    }
    status, metadata = get_vm_info(request_kwargs)

    # check and log the status
    if status == 'RUNNING':
        msg = f'{night_conductor} is running as expected'
        severity = 'INFO'
    else:
        msg = f'{night_conductor} should be running, but its status = {status}'
        severity = 'CRITICAL'
    logger.log_text(msg, severity=severity)

    # in the future, may want to check the metadata as well

    return (status, metadata)

def check_consumer(cue, attrs):
    request_kwargs = {
        'project': PROJECT_ID,
        'zone': ZONE,
        'instance': consumer
    }
    status, metadata = get_vm_info(request_kwargs)

    # determine if we expect the consumer to be running
    expect_consumer_running = True
    if (attrs is not None) and ('KAFKA_TOPIC' in attrs.keys()):
        if attrs['KAFKA_TOPIC'] == 'NONE':
            expect_consumer_running = False

    if cue == 'START':
        # check and log the status
        if status == 'RUNNING':
            if expect_consumer_running:
                msg = f'{consumer} is running as expected'
                severity = 'INFO'
            else:
                msg = f'{consumer} is running, but this is unexpected since KAFKA_TOPIC==NONE'
                severity = 'DEBUG'

        else:
            if expect_consumer_running:
                msg = f'{consumer} should be running, but its status = {status}'
                severity = 'CRITICAL'
            else:
                msg = f'{consumer} is not running; this is expected since KAFKA_TOPIC==NONE'
                severity = 'INFO'

        logger.log_text(msg, severity=severity)

    elif cue == 'END':
        # check and log the status
        if status == 'TERMINATED':
            msg = f'{consumer} has stopped as expected'
            severity = 'INFO'
        else:
            msg = f'{consumer} should have stopped, but its status = {status}'
            severity = 'CRITICAL'
        logger.log_text(msg, severity=severity)

def get_vm_info(request_kwargs):
    request = compute_service.instances().get(**request_kwargs)
    response = request.execute()  # dict

    status = response['status']
    metadata = response['metadata']

    return (status, metadata)


import argparse
import jinja2
import json
import os
import string
import sys

# Schema: performance_models[data_mode][tps] = [model]
performance_models = {}

def add_performance_model(model):
    """
    Adds performance model to the list of performance models.
    """
    # process model values
    model["avgCpu"] = "{:.2f}".format(model["avgCpu"])
    model["avgMem"] = "{:.2f}".format(model["avgMem"])
    model["maxCpu"] = "{:.2f}".format(model["maxCpu"])
    model["maxMem"] = "{:.2f}".format(model["maxMem"])
    model["receivers"].sort()
    model["processors"].sort()
    model["exporters"].sort()
    model["receivers"] = ", ".join(model["receivers"])
    model["processors"] = ", ".join(model["processors"])
    model["exporters"] = ", ".join(model["exporters"])

    data_mode = model["dataMode"].capitalize()
    data_rate = model["dataRate"]

    if data_mode not in performance_models:
        performance_models[data_mode] = {}
    if data_rate not in performance_models[data_mode]:
        performance_models[data_mode][data_rate] = []

    performance_models[data_mode][data_rate].append(model)

def flatten_performance_models():
    """
    Flattens performance model into list of grouped models where each group
    corresponds to a table in the report.
    """
    models_list = []

    for data_mode, data_rates in performance_models.items():
        for data_rate, models in data_rates.items():
            model = {}
            model["data_mode"] = data_mode
            model["data_rate"] = data_rate
            # sort by name and type
            model["models"] = sorted(
                models, key=lambda x: (x["receivers"], x["testcase"], x["dataType"]))
            models_list.append(model)

    # sort by data mode and rate
    models_list = sorted(models_list, key=lambda x: (
        x["data_mode"], x["data_rate"]))
    return models_list

def get_benchmark_entry(model, data_mode, data_rate, value_field, unit, subgroup):
    benchmark_entry = {}
    benchmark_entry["name"] = model["testcase"]
    benchmark_entry["value"] = model[value_field]
    benchmark_entry["unit"] = unit
    benchmark_entry["extra"] = f"{data_mode} (TPS: {data_rate}) - {subgroup}"
    return benchmark_entry

def get_benchmark_data():
    """
    Splits models by testcase and groups by data mode, data rate, and field type.
    """
    benchmark_data = []

    for data_mode, data_rates in performance_models.items():
        for data_rate, models in data_rates.items():
            for model in models:
                benchmark_data.append(get_benchmark_entry(model, data_mode, data_rate, "avgCpu", "%", "Average CPU Usage"))
                benchmark_data.append(get_benchmark_entry(model, data_mode, data_rate, "avgMem", "MB", "Average Memory Usage"))

    return benchmark_data

if __name__ == "__main__":
    parser = argparse.ArgumentParser("Generate performance-report.md and performance-data.json from artifacts")
    parser.add_argument('-v', '--version', help="version to tag the report with", required=True)
    args = parser.parse_args()
    aoc_version = args.version

    templateLoader = jinja2.FileSystemLoader(searchpath="e2etest/templates/")
    env = jinja2.Environment(autoescape=jinja2.select_autoescape(['html', 'xml', 'tpl', 'yaml', 'yml']), loader=templateLoader)

    # get performance models from artifacts
    artifacts_path = "artifacts/"
    commit_id = ""
    collection_period = None
    testing_ami = ""
    for sub_dir in os.listdir(artifacts_path):
        with open(artifacts_path + sub_dir + "/performance.json") as f:
            model = json.load(f)
            commit_id = model["commitId"]
            collection_period = model["collectionPeriod"]
            testing_ami = model["testingAmi"]
            add_performance_model(model)

    models_list = flatten_performance_models()

    # render performance models into markdown
    template = env.get_template('performance_model.tpl')
    rendered_result = template.render({
        "aoc_version": aoc_version,
        "commit_id": commit_id,
        "collection_period": collection_period,
        "testing_ami": testing_ami,
        "models_list": models_list,
    })
    print(rendered_result)

    # write rendered result to report.md
    with open("performance-report.md", "w+") as f:
        f.write(rendered_result)

    # write benchmark-data.json
    with open("performance-data.json", "w+") as f:
        json.dump(get_benchmark_data(), f, indent=4)


import pytest

from opentrons.protocol_api.module_validation_and_errors import (
    validate_heater_shaker_temperature,
    validate_heater_shaker_speed,
    InvalidTargetTemperatureError,
    InvalidTargetSpeedError,
)

@pytest.mark.parametrize("valid_celsius_value", [37.0, 37.1, 50, 94.99, 95])
def test_validate_heater_shaker_temperature(valid_celsius_value: float) -> None:
    """It should return the validated temperature value."""
    validated = validate_heater_shaker_temperature(celsius=valid_celsius_value)
    assert validated == valid_celsius_value

@pytest.mark.parametrize("invalid_celsius_value", [-1, 0, 36.99, 95.01])
def test_validate_heater_shaker_temperature_raises(
    invalid_celsius_value: float,
) -> None:
    """It should raise an error for invalid temperature values."""
    with pytest.raises(InvalidTargetTemperatureError):
        validate_heater_shaker_temperature(celsius=invalid_celsius_value)

@pytest.mark.parametrize("valid_rpm_value", [200, 201, 1000, 2999, 3000])
def test_validate_heater_shaker_speed(valid_rpm_value: int) -> None:
    """It should return the validated speed value."""
    validated = validate_heater_shaker_speed(rpm=valid_rpm_value)
    assert validated == valid_rpm_value

@pytest.mark.parametrize("invalid_rpm_value", [0, 199, 3001])
def test_validate_heater_shaker_speed_raises(invalid_rpm_value: int) -> None:
    """It should raise an error for invalid speed values."""
    with pytest.raises(InvalidTargetSpeedError):
        validate_heater_shaker_speed(rpm=invalid_rpm_value)


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

"""
# File       : ImageEnhancement.py
# Time       ：2023/9/12 11:10
# Author     ：yujia
# version    ：python 3.6
# Description：
"""
import numbers
import cv2
import numpy as np
from PIL import Image

from utils.utils_aug import CenterCrop, ImageNetPolicy, RandomResizedCrop, Resize

class ImageEnhancement(object):
    def __init__(self, input_shape):
        self.resize_crop = RandomResizedCrop(input_shape)
        self.policy = ImageNetPolicy()

        self.resize = Resize(input_shape[0] if input_shape[0] == input_shape[1] else input_shape)
        self.center_crop = CenterCrop(input_shape)

    def rand(self, a=0, b=1):
        return np.random.rand() * (b - a) + a

    def get_random_data(self, image, input_shape, jitter=.3, hue=.1, sat=0.7, val=0.3, random=True):
        # ------------------------------#
        #   获得图像的高宽与目标高宽
        # ------------------------------#
        iw, ih = image.size
        h, w = input_shape

        if not random:
            scale = min(w / iw, h / ih)
            nw = int(iw * scale)
            nh = int(ih * scale)
            dx = (w - nw) // 2
            dy = (h - nh) // 2

            # ---------------------------------#
            #   将图像多余的部分加上灰条
            # ---------------------------------#
            image = image.resize((nw, nh), Image.BICUBIC)
            new_image = Image.new('RGB', (w, h), (128, 128, 128))
            new_image.paste(image, (dx, dy))
            image_data = np.array(new_image, np.float32)

            return image_data

        # ------------------------------------------#
        #   对图像进行缩放并且进行长和宽的扭曲
        # ------------------------------------------#
        new_ar = iw / ih * self.rand(1 - jitter, 1 + jitter) / self.rand(1 - jitter, 1 + jitter)
        scale = self.rand(0.75, 1.5)
        if new_ar < 1:
            nh = int(scale * h)
            nw = int(nh * new_ar)
        else:
            nw = int(scale * w)
            nh = int(nw / new_ar)
        image = image.resize((nw, nh), Image.BICUBIC)

        # ------------------------------------------#
        #   将图像多余的部分加上灰条
        # ------------------------------------------#
        dx = int(self.rand(0, w - nw))
        dy = int(self.rand(0, h - nh))
        new_image = Image.new('RGB', (w, h), (128, 128, 128))
        new_image.paste(image, (dx, dy))
        image = new_image

        # ------------------------------------------#
        #   翻转图像
        # ------------------------------------------#
        flip = self.rand() < .5
        if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)

        rotate = self.rand() < .5
        if rotate:
            angle = np.random.randint(-15, 15)
            a, b = w / 2, h / 2
            M = cv2.getRotationMatrix2D((a, b), angle, 1)
            image = cv2.warpAffine(np.array(image), M, (w, h), borderValue=[128, 128, 128])

        image_data = np.array(image, np.uint8)
        # ---------------------------------#
        #   对图像进行色域变换
        #   计算色域变换的参数
        # ---------------------------------#
        r = np.random.uniform(-1, 1, 3) * [hue, sat, val] + 1
        # ---------------------------------#
        #   将图像转到HSV上
        # ---------------------------------#
        hue, sat, val = cv2.split(cv2.cvtColor(image_data, cv2.COLOR_RGB2HSV))
        dtype = image_data.dtype
        # ---------------------------------#
        #   应用变换
        # ---------------------------------#
        x = np.arange(0, 256, dtype=r.dtype)
        lut_hue = ((x * r[0]) % 180).astype(dtype)
        lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)
        lut_val = np.clip(x * r[2], 0, 255).astype(dtype)

        image_data = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val)))
        image_data = cv2.cvtColor(image_data, cv2.COLOR_HSV2RGB)
        return image_data

    def AutoAugment(self, image, random=True):
        if not random:
            image = self.resize(image)
            image = self.center_crop(image)
            return image

        # ------------------------------------------#
        #   resize并且随即裁剪
        # ------------------------------------------#
        image = self.resize_crop(image)

        # ------------------------------------------#
        #   翻转图像
        # ------------------------------------------#
        flip = self.rand() < .5
        if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)

        # ------------------------------------------#
        #   随机增强
        # ------------------------------------------#
        image = self.policy(image)
        return image

def crop(img, i, j, h, w):
    """Crop the given PIL Image.

    Args:
        img (PIL Image): Image to be cropped.
        i (int): i in (i,j) i.e coordinates of the upper left corner.
        j (int): j in (i,j) i.e coordinates of the upper left corner.
        h (int): Height of the cropped image.
        w (int): Width of the cropped image.

    Returns:
        PIL Image: Cropped image.
    """
    return img.crop((j, i, j + w, i + h))

def center_crop(img, output_size):
    if isinstance(output_size, numbers.Number):
        output_size = (int(output_size), int(output_size))
    w, h = img.size
    th, tw = output_size
    i = int(round((h - th) / 2.))
    j = int(round((w - tw) / 2.))
    return crop(img, i, j, th, tw)

def resize(img, size, interpolation=Image.BILINEAR):
    r"""Resize the input PIL Image to the given size.

    Args:
        img (PIL Image): Image to be resized.
        size (sequence or int): Desired output size. If size is a sequence like
            (h, w), the output size will be matched to this. If size is an int,
            the smaller edge of the image will be matched to this number maintaing
            the aspect ratio. i.e, if height > width, then image will be rescaled to
            :math:`\left(\text{size} \times \frac{\text{height}}{\text{width}}, \text{size}\right)`
        interpolation (int, optional): Desired interpolation. Default is
            ``PIL.Image.BILINEAR``

    Returns:
        PIL Image: Resized image.
    """
    if isinstance(size, int):
        w, h = img.size
        if (w <= h and w == size) or (h <= w and h == size):
            return img
        if w < h:
            ow = size
            oh = int(size * h / w)
            return img.resize((ow, oh), interpolation)
        else:
            oh = size
            ow = int(size * w / h)
            return img.resize((ow, oh), interpolation)
    else:
        return img.resize(size[::-1], interpolation)

def letterbox_image(image, size, letterbox_image=False):
    w, h = size
    iw, ih = image.size
    if letterbox_image:
        '''resize image with unchanged aspect ratio using padding'''
        scale = min(w/iw, h/ih)
        nw = int(iw*scale)
        nh = int(ih*scale)

        image = image.resize((nw,nh), Image.BICUBIC)
        new_image = Image.new('RGB', size, (128,128,128))
        new_image.paste(image, ((w-nw)//2, (h-nh)//2))
    else:
        if h == w:
            new_image = resize(image, h)
        else:
            new_image = resize(image, [h ,w])
        new_image = center_crop(new_image, [h ,w])
    return new_image


from .base import BaseOrder, BaseUserOrder
from datetime import timedelta
from decimal import Decimal

from django.conf import settings
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext as _

from core.models import Transaction, Currency
from payments.utils import money_format
from payments.models import Payment, PaymentPreference
from ticker.models import Price
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db.models import Q
from math import log10, floor, ceil
from django.utils.translation import activate
from nexchange.utils import send_email, send_sms, get_nexchange_logger
from django_fsm import FSMIntegerField, transition
from nexchange.celery import app
from cached_property import cached_property_with_ttl
from payments.api_clients.safe_charge import SafeChargeAPIClient
from nexchange.api_clients.factory import ApiClientFactory
from oauth2_provider.models import AccessToken
from decimal import InvalidOperation
from verification.api_clients.idenfy import IdenfyAPIClient
from verification.models import IdentityToken
from payments.utils import get_first_name, get_last_name
from .fee import OrderFee, FeeSource

safe_charge_client = SafeChargeAPIClient()
idenfy_client = IdenfyAPIClient()
factory = ApiClientFactory()
logger = get_nexchange_logger('Order logger', with_email=True,
                              with_console=True)

class Order(BaseOrder, BaseUserOrder):

    def __init__(self, *args, **kwargs):
        self.fee_list = {'update': False}
        super(Order, self).__init__(*args, **kwargs)

    # these are redifined to pass to django FSM transitions
    INITIAL = BaseUserOrder.INITIAL
    PAID_UNCONFIRMED = BaseUserOrder.PAID_UNCONFIRMED
    PAID = BaseUserOrder.PAID
    PRE_RELEASE = BaseUserOrder.PRE_RELEASE
    RELEASED = BaseUserOrder.RELEASED
    COMPLETED = BaseUserOrder.COMPLETED
    CANCELED = BaseUserOrder.CANCELED
    REFUNDED = BaseUserOrder.REFUNDED
    STATUS_TYPES = BaseUserOrder.STATUS_TYPES
    _order_status_help = BaseUserOrder._order_status_help

    RETRY_RELEASE = 'orders.task_summary.release_retry_invoke'

    PROVIDED_BASE = 0
    PROVIDED_QUOTE = 1
    PROVIDED_BOTH = 2
    PROVIDED_AMOUNT_OPTIONS = (
        (PROVIDED_BASE, _('amount_base')),
        (PROVIDED_QUOTE, _('amount_quote')),
        (PROVIDED_BOTH, _('amount_quote and amount_base')),
    )

    # Todo: inherit from BTC base?, move lengths to settings?
    status = FSMIntegerField(choices=STATUS_TYPES, default=INITIAL,
                             help_text=_order_status_help)
    payment_window = models.IntegerField(default=settings.PAYMENT_WINDOW)
    unpaid_order_window = models.IntegerField(
        default=settings.UNPAID_CANCEL_WINDOW_MINUTES
    )
    payment_preference = models.ForeignKey('payments.PaymentPreference',
                                           default=None,
                                           null=True, blank=True,
                                           on_delete=models.DO_NOTHING)
    is_default_rule = models.BooleanField(default=False)
    from_default_rule = models.BooleanField(default=False)
    price = models.ForeignKey(Price, null=True, blank=True,
                              on_delete=models.DO_NOTHING)
    user_marked_as_paid = models.BooleanField(default=False)
    system_marked_as_paid = models.BooleanField(default=False)
    user_provided_amount = models.IntegerField(
        choices=PROVIDED_AMOUNT_OPTIONS, default=PROVIDED_BASE)
    slippage = models.DecimalField(
        max_digits=18, decimal_places=8, default=Decimal('0'),
    )
    set_as_paid_unconfirmed_on = models.DateTimeField(null=True, blank=True,
                                                      default=None)
    set_as_paid_on = models.DateTimeField(null=True, blank=True, default=None)
    set_as_released_on = models.DateTimeField(null=True, blank=True,
                                              default=None)
    return_identity_token = models.BooleanField(
        default=True,
        help_text=_('If True - user can use auto id checking service(Idenfy).')
    )
    referred_with = models.ForeignKey(
        'referrals.ReferralCode', default=None, null=True, blank=True,
        on_delete=models.DO_NOTHING
    )

    class Meta:
        ordering = ['-created_on']
        # unique_together = ['deleted', 'unique_reference']

    def validate_unique(self, exclude=None):
        # TODO: exclude expired?
        if not self.deleted and \
                Order.objects.exclude(pk=self.pk).filter(
                    unique_reference=self.unique_reference,
                    deleted=False).exists():
            raise ValidationError(
                'Un-deleted order with same reference exists')

        super(Order, self).validate_unique(exclude=exclude)

    def _types_range_constraint(self, field, types):
        """ This is used for validating IntegerField's with choices.
        Assures that value is in range of choices.
        """
        if field > max([i[0] for i in types]):
            raise ValidationError(_('Invalid order type choice'))
        elif field < min([i[0] for i in types]):
            raise ValidationError(_('Invalid order type choice'))

    def get_amount_base_min(self, user_format=False):
        _base_min = self.pair.base.minimal_amount
        if not user_format:
            return _base_min
        _quote_min = self.get_amount_quote_min(user_format=False)
        if _quote_min is None:
            return _base_min
        _min_list = [_base_min]
        _order = Order(pair=self.pair, amount_quote=_quote_min,
                       price=self.price)
        _order.calculate_base_from_quote()
        diff = settings.SATOSHI if self.pair.quote.is_crypto else Decimal('0')
        _min_list.append(_order.amount_base + diff)
        return max(_min_list)

    def get_amount_base_max(self, user_format=False):
        if self.pk:
            return
        _base_max = None if self.pair.base.execute_cover \
            else self.pair.base.available_main_reserves
        if not user_format:
            return _base_max
        _quote_max = self.get_amount_quote_max(user_format=False)
        if _quote_max is None:
            return _base_max
        _max_list = [_base_max] if _base_max else []
        _order = Order(pair=self.pair, amount_quote=_quote_max,
                       price=self.price)
        _order.calculate_base_from_quote()
        diff = settings.SATOSHI if self.pair.base.is_crypto else Decimal('0')
        _max_list.append(_order.amount_base - diff)
        return min(_max_list)

    def get_amount_quote_max(self, user_format=False):
        if self.pk:
            return
        _quote_max = self.pair.quote.maximal_amount
        if not user_format:
            return _quote_max
        _base_max = self.get_amount_base_max(user_format=False)
        if not _base_max:
            return _quote_max
        _max_list = [_quote_max]
        _order = Order(pair=self.pair, amount_base=_base_max,
                       price=self.price)
        _order.calculate_quote_from_base()
        diff = settings.SATOSHI if self.pair.quote.is_crypto else Decimal('0')
        _max_list.append(_order.amount_quote - diff)
        return min(_max_list)

    def get_amount_quote_min(self, user_format=False):
        if self.pk:
            return
        _quote_min = \
            None if self.pair.is_crypto else self.pair.quote.minimal_amount
        if not user_format:
            return _quote_min
        _base_min = self.get_amount_base_min(user_format=False)
        if not _base_min:
            return _quote_min
        _min_list = [_quote_min] if _quote_min else []
        _order = Order(pair=self.pair, amount_base=_base_min,
                       price=self.price)
        _order.calculate_quote_from_base()
        diff = settings.SATOSHI if self.pair.quote.is_crypto else Decimal('0')
        _min_list.append(_order.amount_quote + diff)
        return max(_min_list)

    def _validate_order_amount(self):
        _raise_error = False
        # _error_type is either 'min' or 'max'
        _error_type = None
        _error_amount_type = \
            'amount_base' if self.user_provided_amount == self.PROVIDED_BASE \
            else 'amount_quote'
        # _direction is either 'receive' or 'deposit' (terms form UI)
        _direction = \
            'receive' if self.user_provided_amount == self.PROVIDED_BASE \
            else 'deposit'
        _currency_code = \
            self.pair.base.code \
            if self.user_provided_amount == self.PROVIDED_BASE \
            else self.pair.quote.code
        # actual restricted amount (might be converted)
        _final_amount = None
        # Message template - end user must understand this
        _error_template = \
            '{_error_type}imum {_direction} amount is {_final_amount} ' \
            '{_currency_code} on this trade.'

        for _amount_type in ['amount_quote', 'amount_base']:
            _min = getattr(self, 'get_{}_min'.format(_amount_type))()
            _max = getattr(self, 'get_{}_max'.format(_amount_type))()
            _amount = getattr(self, _amount_type)
            if _min is not None and _amount < _min:
                _raise_error = True
                _error_type = 'min'
                break
            if _max is not None and _amount > _max:
                _raise_error = True
                _error_type = 'max'
                break

        if _raise_error:
            _final_amount = getattr(
                self, 'get_{}_{}'.format(_error_amount_type, _error_type)
            )(user_format=True)
            if _final_amount is None:
                _error_msg = \
                    'Cannot initiate this trade, please contact support.'
            else:
                _error_msg = _error_template.format(
                    _error_type=_error_type.capitalize(),
                    _direction=_direction,
                    _currency_code=_currency_code,
                    _final_amount=str(_final_amount).rstrip('0').rstrip('.'),
                )
            raise ValidationError(_error_msg)

    def _validate_status(self, status):
        if not self.pk:
            return
        old_order = Order.objects.get(pk=self.pk)
        old_status = old_order.status
        if old_status == status:
            return
        elif old_status == Order.CANCELED and status != Order.CANCELED:
            raise ValidationError(
                _('Cannot set order status to {} when it is CANCELED'.format(
                    status)))
        elif status == Order.CANCELED:
            if old_status in [Order.INITIAL, Order.CANCELED,
                              Order.PAID_UNCONFIRMED]:
                return
            else:
                raise ValidationError(
                    _('Cannot CANCEL order which is status is {}'.format(
                        old_order.get_status_display()
                    ))
                )
        elif status == Order.REFUNDED:
            if old_status in self.REFUNDABLE_STATES:
                return
            else:
                raise ValidationError(
                    _('Cannot REFUND order which is status is {}'.format(
                        old_order.get_status_display()
                    ))
                )
        elif status < old_status:
            raise ValidationError(
                _('Cannot set order status from {} to {}'
                  '(must be incremential)'.format(old_status, status)))
        else:
            return

    def _validate_pair(self):
        if not self.pk:
            self.pair.refresh_from_db()
            self.pair.validate_enabled()
            self.pair.validate_user(self.user)

    def _validate_price(self):
        if not self.pk and self.price.expired:
            error_msg = 'Pair {} is temporarily unavailable.'.format(
                self.pair.name
            )
            logger.warning('{}. Ticker expired !!!'.format(error_msg))
            raise ValidationError(
                _(error_msg.format(self.pair.name))
            )

    def _validate_reserves(self):
        error_msg = 'Pair {} is temporarily unavailable.'.format(
            self.pair.name
        )
        if not self.pk:
            if self.pair.base.is_crypto:
                base_reserves = self.pair.base.available_reserves
                min_base_reserves = self.pair.base.reserve.minimum_level
                if base_reserves < min_base_reserves:
                    logger.warning(
                        '{}. Base Reserves too low.'.format(error_msg)
                    )
                    raise ValidationError(_(error_msg))
            if self.pair.quote.is_crypto:
                quote_reserves = self.pair.quote.available_reserves
                min_quote_reserves = self.pair.quote.reserve.maximum_level
                if quote_reserves > min_quote_reserves:
                    logger.warning('{}. Quote Reserves too high.'.format(
                        error_msg
                    ))
                    logger.warning(error_msg)
                    raise ValidationError(_(error_msg))

    def _validate_address(self):
        if not self.pk and getattr(self.withdraw_address, 'blocked', False):
            raise ValidationError(_('Address {} is blocked.'.format(
                self.withdraw_address.address
            )))

    def _validate_fields(self):
        self._types_range_constraint(self.order_type, self.TYPES)
        self._types_range_constraint(self.status, self.STATUS_TYPES)
        self._validate_status(self.status)
        self._validate_pair()
        self._validate_address()
        if all([not self.amount_base, not self.amount_quote]):
            raise ValidationError(
                _('One of amount_quote and amount_base is required.'))
        if all([not self.pair.includes_currency('XMR'),
                self.payment_id is not None]):
            raise ValidationError(
                _('Payment id is currently used only for Monero address'))
        if all([not self.pair.includes_currency('XRP'),
                self.destination_tag is not None]):
            raise ValidationError(
                _('Destination tag is currently used only for Ripple address'))

    def clean(self, *args, **kwargs):
        self._validate_fields()
        super(Order, self).clean(*args, **kwargs)

    def set_provided_amount_option(self):
        if not self.pk:
            self.user_provided_amount = self.get_provided_amount_option()

    def get_provided_amount_option(self):
        if all([self.amount_quote, self.amount_base]):
            return self.PROVIDED_BOTH
        elif self.amount_base:
            return self.PROVIDED_BASE
        elif self.amount_quote:
            return self.PROVIDED_QUOTE

    def set_payment_preference(self, method_name='Safe Charge'):
        if any([self.pair.is_crypto, self.pk, self.payment_preference]):
            return
        self.payment_preference = PaymentPreference.objects.get(
            user__is_staff=True,
            payment_method__name__icontains=method_name
        )

    def save(self, *args, **kwargs):
        self._validate_fields()
        if not self.unique_reference:
            self.unique_reference = \
                self.gen_unique_value(
                    lambda x: self.get_random_unique_reference(x),
                    lambda x: Order.objects.filter(unique_reference=x).count(),
                    settings.UNIQUE_REFERENCE_LENGTH
                )
        if all([self.pair.quote.code == 'XMR', not self.payment_id]):
            self.payment_id = self.gen_unique_payment_id(32)
        if all([self.pair.quote.code == 'XRP', not self.destination_tag]):
            self.destination_tag = \
                self.gen_destination_tag(
                    lambda: self.get_random_integer(),
                    lambda x: Order.objects.filter(destination_tag=x).count()
                )
        if self.pair.is_crypto:
            self.exchange = True
        else:
            self.exchange = False
        if not self.pk:
            if self.amount_base:
                self.calculate_quote_from_base()
            elif self.amount_quote:
                self.calculate_base_from_quote()
        self._validate_order_amount()
        self._validate_price()
        self._validate_reserves()
        self.referred_with = self._referred_with
        super(Order, self).save(*args, **kwargs)
        if self.fee_list.get('update', False):
            tot_fee_amount_base = tot_fee_amount_quote = Decimal('0')
            for _fee in self.fee_list.get('data', []):
                fee_source = _fee.get('fee_source')
                amount_quote = _fee.get('amount_quote')
                amount_base = _fee.get('amount_base')
                fee, _ = OrderFee.objects.get_or_create(
                    fee_source=fee_source, order=self
                )
                fee.amount_base = amount_base
                tot_fee_amount_base += \
                    amount_base if amount_base else Decimal('0')
                fee.amount_quote = amount_quote
                tot_fee_amount_quote += \
                    amount_quote if amount_quote else Decimal('0')
                fee.save()
            markup_multiplier = \
                Decimal('1') / (Decimal('1') + self.pair.fee_ask_current) \
                * self.pair.fee_ask_current
            markup_base = \
                (self.amount_base - tot_fee_amount_base) * markup_multiplier
            markup_quote = \
                (self.amount_quote - tot_fee_amount_quote) * markup_multiplier
            markup_fee, _ = OrderFee.objects.get_or_create(
                fee_source=self.get_or_create_fee_source(OrderFee.MARKUP),
                order=self
            )
            markup_fee.amount_base = markup_base
            markup_fee.amount_quote = markup_quote
            markup_fee.save()
            self.fee_list.update({'update': False})
        if not self.exchange and self.payment_set.count():
            payment = self.payment_set.last()
            if payment and payment.payment_preference:
                pref = payment.payment_preference
                pref.save(update_fields=['has_pending_orders'])

    def calculate_quote_from_base(self, price=None):
        try:
            self.calculate_from(_from='base', price=price)
        except InvalidOperation:
            raise ValidationError(
                _('It is not possible to receive {} {}'.format(
                    self.amount_base,
                    self.pair.base.code
                ))
            )

    def calculate_base_from_quote(self, price=None):
        try:
            self.calculate_from(_from='quote', price=price)
        except InvalidOperation:
            raise ValidationError(
                _('It is not possible to deposit {} {}'.format(
                    self.amount_quote,
                    self.pair.quote.code
                ))
            )

    def set_slippage(self, price, amount, amount_type):
        currency = self.pair.base
        quote = self.pair.quote
        if amount_type == 'base':
            additional_amount = amount
        elif amount_type == 'quote':
            additional_amount = amount / price.rate

        slippage = self.get_current_slippage(
            currency, quote, additional_amount=additional_amount
        )
        self.slippage = slippage
        currency.current_slippage = slippage
        currency.save()
        price.slippage = slippage
        price.save()

    def convert_from(self, amount, _from):
        if amount is None or not self.price:
            return
        rate = self.price.ticker.ask if self.order_type == self.BUY else self.\
            price.ticker.bid
        if _from == 'base':
            return amount * rate
        elif _from == 'quote':
            return amount / rate

    def get_or_create_fee_source(self, name):
        sources = FeeSource.objects.filter(name=name)
        return sources.latest('id') if sources else FeeSource.objects.create(
            name=name
        )

    def calculate_from(self, _from='base', price=None):
        self.set_provided_amount_option()
        self.set_payment_preference()
        if _from == 'base':
            _to = 'quote'
        elif _from == 'quote':
            _to = 'base'
        else:
            raise NotImplementedError('Not implemented conversion')
        amount_input = Decimal(getattr(self, 'amount_{}'.format(_from)))
        setattr(self, 'amount_{}'.format(_from), amount_input)
        if not price:
            price = self.pair.latest_price
        self.price = price
        self.set_slippage(self.price, amount_input, _from)
        ticker_amount_output = getattr(self, 'ticker_amount_{}'.format(_to))
        fee_adder = getattr(self, 'add_payment_fee_to_amount_{}'.format(_to))
        amount_output, withdrawal_fee, payment_fee = fee_adder(
            ticker_amount_output
        )
        self.fee_list.update({
            'update': True,
            'data': [
                {'amount_{}'.format(_to): withdrawal_fee,
                 'amount_{}'.format(_from): self.convert_from(withdrawal_fee,
                                                              _to),
                 'fee_source': self.get_or_create_fee_source(
                     OrderFee.WITHDRAWAL),
                 'order': self},
                {'amount_{}'.format(_to): payment_fee,
                 'amount_{}'.format(_from): self.convert_from(payment_fee,
                                                              _to),
                 'fee_source': self.get_or_create_fee_source(
                     OrderFee.PAYMENT_METHOD),
                 'order': self},
            ]})
        decimal_places_output = getattr(
            self, 'recommended_{}_decimal_places'.format(_to))
        amount_output_formatted = money_format(amount_output,
                                               places=decimal_places_output)
        setattr(self, 'amount_{}'.format(_to), amount_output_formatted)
        decimal_places_input = getattr(
            self, 'recommended_{}_decimal_places'.format(_from))
        amount_input_formatted = money_format(amount_input,
                                              places=decimal_places_input)
        setattr(self, 'amount_{}'.format(_from), amount_input_formatted)

    @property
    def payment_url(self):
        if any([self.pair.quote.is_crypto, self.status != self.INITIAL]):
            return
        return safe_charge_client.generate_cachier_url_for_order(self)

    def get_fullname(self):
        if self.payment_set.count() == 1:
            pref = self.payment_set.get().payment_preference
            fullname = pref.secondary_identifier
            return fullname if fullname else ''
        return ''

    def get_or_create_identity_token(self):
        try:
            token = IdentityToken.objects.filter(order=self).latest('id')
        except ObjectDoesNotExist:
            token = None
        if not token or token.expired or token.used:
            fullname = self.get_fullname()
            first_name = get_first_name(fullname)
            last_name = get_last_name(fullname)
            _token, _scan_ref = idenfy_client.get_token_for_order(
                self,
            )
            if not _token:
                return
            token = IdentityToken.objects.create(order=self, token=_token,
                                                 first_name=first_name,
                                                 last_name=last_name,
                                                 scan_ref=_scan_ref)
        return token

    @property
    def identity_token(self):
        if any([self.pair.quote.is_crypto,
                self.status != self.PAID_UNCONFIRMED,
                not self.return_identity_token]):
            return
        token = self.get_or_create_identity_token()
        return token.token if token else None

    @property
    def identity_check_url(self):
        token = self.identity_token
        return idenfy_client.get_redirect_url(token)

    @cached_property_with_ttl(ttl=settings.TICKER_INTERVAL)
    def amount_eur(self):
        return Price.convert_amount(self.amount_quote,
                                    self.pair.quote, 'EUR')

    @cached_property_with_ttl(ttl=settings.TICKER_INTERVAL)
    def amount_usd(self):
        return Price.convert_amount(self.amount_quote,
                                    self.pair.quote, 'USD')

    @cached_property_with_ttl(ttl=settings.TICKER_INTERVAL)
    def amount_btc(self):
        return Price.convert_amount(self.amount_quote,
                                    self.pair.quote, 'BTC')

    @property
    def ticker_amount_quote(self):
        if not self.price:
            return self.amount_quote
        # For annotations
        res = None
        if self.order_type == Order.BUY:
            res = self.amount_base * self.price.rate
        elif self.order_type == Order.SELL:
            res = self.amount_base * self.price.ticker.bid
        return res

    @property
    def ticker_amount_base(self):
        if not self.price:
            return self.amount_base
        # For annotations
        res = None
        if self.order_type == Order.BUY:
            res = self.amount_quote / self.price.rate
        elif self.order_type == Order.SELL:
            res = self.amount_quote / self.price.ticker.bid
        return res

    @property
    def dynamic_decimal_places(self):

        return False

    @property
    def recommended_quote_decimal_places(self):
        return self.recommended_decimal_places(self.ticker_amount_quote,
                                               self.pair.quote,
                                               dynamic=self.dynamic_decimal_places)  # noqa

    @property
    def recommended_base_decimal_places(self):
        return self.recommended_decimal_places(self.ticker_amount_base,
                                               self.pair.base,
                                               dynamic=self.dynamic_decimal_places)  # noqa

    @property
    def withdrawal_fee(self):
        return self.pair.base.withdrawal_fee

    @property
    def withdrawal_fee_quote(self):
        if self.order_type == self.BUY:
            return self.pair.base.withdrawal_fee * self.price.ticker.ask
        if self.order_type == self.SELL:
            return self.pair.base.withdrawal_fee * self.price.ticker.bid

    def recommended_decimal_places(self, amount, currency, dynamic=False):
        decimal_places = currency.rounding
        if dynamic and currency.is_crypto:
            add_places = -int(floor(log10(abs(amount))))
            _add_places = add_places if add_places > 0 else 0
            decimal_places = 2 + _add_places
        return decimal_places

    def _get_minimal_payment_method_fee(self, currency):
        payment_method = self.payment_preference.payment_method
        min_fee = payment_method.minimal_fee_amount
        min_curr = payment_method.minimal_fee_currency
        return Price.convert_amount(min_fee, min_curr, currency)

    @property
    def minimal_payment_method_fee_quote(self):
        return self._get_minimal_payment_method_fee(self.pair.quote)

    @property
    def minimal_payment_method_fee_base(self):
        return self.minimal_payment_method_fee_quote / self.price.rate

    def add_payment_fee_to_amount_base(self, amount_base):
        payment_fee = None
        if any([not self.payment_preference, self.pair.is_crypto]):
            res = amount_base
        else:
            method = self.payment_preference.payment_method
            fee = method.fee_deposit
            fee_amount = fee * amount_base
            if fee_amount >= self.minimal_payment_method_fee_base:
                payment_fee = fee_amount
                res = amount_base - fee_amount
            else:
                payment_fee = self.minimal_payment_method_fee_base
                res = amount_base - self.minimal_payment_method_fee_base
        withdrawal_fee = self.withdrawal_fee
        return res - withdrawal_fee, withdrawal_fee, payment_fee

    def add_payment_fee_to_amount_quote(self, amount_quote):
        withdrawal_fee = self.withdrawal_fee_quote
        payment_fee = None
        amount_quote += self.withdrawal_fee_quote

        if any([not self.payment_preference, self.pair.is_crypto]):
            return amount_quote, self.withdrawal_fee_quote, payment_fee

        base = Decimal('1.0')
        fee = Decimal('0.0')
        method = self.payment_preference.payment_method
        if self.order_type == self.BUY:
            fee = method.fee_deposit
            if method.pays_deposit_fee == method.MERCHANT:
                fee = -fee
        elif self.order_type == self.SELL:
            fee = self.payment_preference.payment_method.fee_withdraw
            if method.pays_withdraw_fee == method.MERCHANT:
                fee = -fee
        res = amount_quote / (base - fee)
        if res - amount_quote < self.minimal_payment_method_fee_quote:
            res = amount_quote + self.minimal_payment_method_fee_quote
        payment_fee = res - amount_quote
        return res, withdrawal_fee, payment_fee

    @property
    def is_paid(self):
        if self.order_type == self.BUY and not self.exchange:
            return self.is_paid_buy
        elif self.order_type == self.SELL:
            raise NotImplementedError('Exists only for BUY orders.')

    @property
    def success_payments_amount(self):
        payments = self.success_payments_by_reference
        if not payments:
            payments = self.success_payments_by_wallet
        sum_success = Decimal(0)
        for p in payments:
            sum_success += p.amount_cash
        return sum_success

    @property
    def success_payments_by_reference(self):
        ref = self.unique_reference
        payments = Payment.objects.filter(
            is_success=True, reference=ref, currency=self.pair.quote)
        return payments

    @property
    def bad_currency_payments(self):
        payments = self.payment_set.filter(~Q(currency=self.pair.quote))
        return payments

    @property
    def success_payments_by_wallet(self):
        method = self.payment_preference.payment_method
        payments = Payment.objects.filter(
            is_success=True,
            user=self.user,
            amount_cash=self.amount_quote,
            payment_preference__payment_method=method,
            currency=self.pair.quote,
        ).filter(Q(order=self) | Q(order=None))
        return payments

    @property
    def is_paid_buy(self):
        sum_all = self.success_payments_amount
        amount_expected = (
            self.amount_quote -
            self.payment_preference.payment_method.allowed_amount_unpaid
        )
        if sum_all >= amount_expected:
            return True
        return False

    @property
    def part_paid_buy(self):
        if self.order_type != self.BUY:
            return False
        sum_all = self.success_payments_amount
        amount_expected = self.amount_quote
        return sum_all / amount_expected

    @property
    def is_buy(self):
        return self.order_type == self.BUY

    @property
    def requires_withdraw_address(self):
        return (self.order_type == self.BUY) or self.exchange

    @property
    def payment_deadline(self):
        """returns datetime of payment_deadline (creation + payment_window)"""
        # TODO: Use this for pay until message on 'order success' screen
        return self.created_on + timedelta(minutes=self.payment_window)

    @property
    def kyc_deadline(self):
        payment = self.payment_set.last()
        if payment:
            return payment.kyc_void_time

    @property
    def expired(self):
        """Is expired if payment_deadline is exceeded and it's not paid yet"""
        # TODO: validate this business rule
        # TODO: Refactor, it is unreasonable to have different standards of
        # time in the DB
        return (timezone.now() > self.payment_deadline) and \
               (self.status not in Order.IN_PAID + [Order.PAID_UNCONFIRMED])

    @property
    def unpaid_order_expired(self):
        """Is expired when an order has not been payed within 1hr"""
        return (timezone.now() - self.created_on > timedelta(
                minutes=self.unpaid_order_window + self.payment_window)
                ) and (self.status == Order.INITIAL)

    @property
    def payment_status_frozen(self):
        """return a boolean indicating if order can be updated
        Order is frozen if it is expired or has been paid
        """
        # TODO: validate this business rule
        return self.expired or self.status in Order.IN_RELEASED

    @property
    def target(self):
        """return target currency"""
        if self.order_type == self.BUY:
            return self.pair.base.code
        else:
            return self.pair.quote.code

    @property
    def withdrawal_address_frozen(self):
        """return bool whether the withdraw address can
           be changed"""
        return self.status in Order.IN_RELEASED

    @property
    def rate(self):
        return money_format(self._rate, places=8)

    @property
    def inverted_rate(self):
        return money_format(self.amount_base / self.amount_quote, places=8)

    @classmethod
    def pending_amount_diff(cls, currency, additional_amount=0):
        if not isinstance(additional_amount, Decimal):
            additional_amount = Decimal(str(additional_amount))
        return additional_amount
        # FIXME: only use additional_amount for now, logic down is for reading order list  # noqa
        # if not isinstance(currency, Currency):
        #     currency = Currency.objects.get(code=currency)
        # if not isinstance(additional_amount, Decimal):
        #     additional_amount = Decimal(str(additional_amount))
        # base = cls.objects.filter(
        #     pair__base=currency, status=cls.PAID_UNCONFIRMED).aggregate(
        #     Sum('amount_base'))['amount_base__sum']
        # quote = cls.objects.filter(
        #     pair__quote=currency, status=cls.PAID_UNCONFIRMED).aggregate(
        #     Sum('amount_quote'))['amount_quote__sum']
        # if not base:
        #     base = Decimal('0')
        # base += additional_amount
        # if not quote:
        #     quote = Decimal('0')
        # return base - quote

    @classmethod
    def get_current_slippage(cls, currency, quote, additional_amount=0):
        slippage_rate = \
            currency.slippage_rate * quote.quote_slippage_rate_multiplier  # noqa
        unslippaged_amount = \
            currency.unslippaged_amount * quote.quote_unslippaged_amount_multiplier  # noqa
        if not isinstance(currency, Currency):
            currency = Currency.objects.get(code=currency)
        diff = cls.pending_amount_diff(
            currency, additional_amount=additional_amount
        )
        if diff < unslippaged_amount:
            return Decimal('0')
        else:
            return (diff - unslippaged_amount) * slippage_rate  # noqa

    def get_profile(self):
        return self.user.profile

    def notify(self):
        profile = self.get_profile()

        # Activate translation
        if any([profile.notify_by_email, profile.notify_by_phone]):
            activate(profile.lang)

        status_verb = self.get_status_display().lower()
        if self.status == self.INITIAL:
            status_verb = 'created'
        elif self.status == self.PAID_UNCONFIRMED:
            status_verb = 'paid(waiting for confirmation)'

        title = _(
            'Nexchange: Order {ref} {status_verb}'.format(
                ref=self.unique_reference,
                status_verb=status_verb
            )
        )
        msg = _('Your order {ref} is {status_verb}.').format(
            ref=self.unique_reference,
            status_verb=status_verb
        )
        if self.withdraw_address:
            msg += ' Withdraw address: {withdraw_address}.'.format(
                withdraw_address=self.withdraw_address.address
            )
            withdraw_transaction = self.transactions.filter(
                type=Transaction.WITHDRAW).last()
            if withdraw_transaction is not None:
                tx_id = withdraw_transaction.tx_id
                if tx_id is not None:
                    msg += ' Transaction id: {tx_id}.'.format(
                        tx_id=tx_id)
        if self.status == self.RELEASED and self.suggest_trustpilot:
            msg += ' You can rate our service on Trustpilot ' \
                   'https://www.trustpilot.com/review/n.exchange'

        # send sms depending on notification settings in profile
        if profile.notify_by_phone and profile.phone:
            phone_to = str(profile.phone)
            send_sms(msg, phone_to)

        # send email
        if profile.notify_by_email and profile.user.email:
            send_email(profile.user.email, title, msg)

    def __str__(self):
        name = \
            '{status} {type} {unique_reference} {pair} {amount_base} {base} ' \
            '{amount_quote} {quote} {username}'.format(
                username=self.user.username,
                type=self.get_order_type_display(),
                pair=self.pair.name,
                amount_base=str(self.amount_base).rstrip('0').rstrip('.'),
                amount_quote=str(self.amount_quote).rstrip('0').rstrip('.'),
                base=self.pair.base.code,
                quote=self.pair.quote.code,
                status=self.get_status_display(),
                unique_reference=self.unique_reference
            )
        return name

    def calculate_order(self, amount_quote, payment_method=None):
        if self.expired:
            price = Price.objects.filter(
                pair=self.pair, market__is_main_market=True).latest('id')
            now = timezone.now()
            if now > self.payment_deadline:
                expired_minutes = ceil(
                    (now - self.payment_deadline).seconds / 60)
            else:
                expired_minutes = 0
            self.payment_window += settings.PAYMENT_WINDOW + expired_minutes
        else:
            price = self.price
        new_payment_method = False
        if payment_method:
            if not self.payment_preference:
                new_payment_method = True
            elif self.payment_preference.payment_method != payment_method:
                new_payment_method = True

        if any([self.expired, self.amount_quote != amount_quote,
                new_payment_method]):
            if new_payment_method:
                self.payment_preference = PaymentPreference.objects.get(
                    user__is_staff=True,
                    payment_method=payment_method
                )
            self.amount_quote = amount_quote
            self.calculate_base_from_quote(price=price)
            self.save()

    @transition(field=status, source=INITIAL, target=PAID_UNCONFIRMED)
    def _register_deposit(self, tx_data, crypto=True):
        if crypto:
            model = Transaction
            amount_key = 'amount'
        else:
            model = Payment
            amount_key = 'amount_cash'
        order = tx_data.get('order')
        tx_type = tx_data.get('type')
        tx_amount = tx_data.get(amount_key)
        tx_currency = tx_data.get('currency')
        if order.pair.quote.code == 'XRP':
            tx_destination_tag = tx_data.get('destination_tag')
            if order.destination_tag != tx_destination_tag:
                raise ValidationError(
                    'Bad tx destination tag {}. Should be {}'.format(
                        order, self
                    )
                )
        if order.pair.quote.code == 'XMR':
            tx_payment_id = tx_data.get('payment_id')
            if order.payment_id != tx_payment_id:
                raise ValidationError(
                    'Bad tx payment id {}. Should be {}'.format(
                        order, self
                    )
                )

        if order != self:
            raise ValidationError(
                'Bad order {} on the deposit tx. Should be {}'.format(
                    order, self
                )
            )
        if self.pair.quote != tx_currency:
            raise ValidationError(
                'Bad tx currency {}. Order quote currency {}'.format(
                    tx_currency, self.pair.quote
                )
            )
        if tx_type != Transaction.DEPOSIT:
            raise ValidationError(
                'Order {}. Cannot register DEPOSIT - wrong transaction '
                'type {}'.format(self, tx_type))

        if not tx_amount:
            raise ValidationError(
                'Order {}. Cannot register DEPOSIT - bad amount - {}'.format(
                    self, tx_amount))

        # Transaction is created before calculate_order to assure that
        # it will not be hanging (waiting for better rate).

        tx = model(**tx_data)
        tx.save()
        payment_method = None
        if not crypto:
            payment_method = tx.payment_preference.payment_method

        self.calculate_order(tx_amount, payment_method=payment_method)
        self.set_as_paid_unconfirmed_on = timezone.now()

        return tx

    def register_deposit(self, tx_data, crypto=True):
        self.refresh_from_db()
        res = {'status': 'OK'}
        try:
            tx = self._register_deposit(tx_data, crypto=crypto)
            res.update({'tx': tx})
        except Exception as e:
            res = {'status': 'ERROR', 'message': '{}'.format(e)}
            self.refresh_from_db()
        self.save()
        return res

    @transition(field=status, source=PAID_UNCONFIRMED, target=PAID)
    def _confirm_deposit(self, tx, crypto=True):
        if crypto:
            success_params = ['is_completed', 'is_verified']
        else:
            success_params = ['is_complete']
        if tx.type != Transaction.DEPOSIT:
            raise ValidationError(
                'Order {}. Cannot confirm DEPOSIT - wrong transaction '
                'type {}'.format(self, tx.type))
        for param in success_params:
            if getattr(tx, param):
                raise ValidationError(
                    'Order {}.Cannot confirm DEPOSIT - already confirmed({}).'
                    ''.format(self, param)
                )
            setattr(tx, param, True)
        tx.save()
        self.set_as_paid_on = timezone.now()

    def confirm_deposit(self, tx, crypto=True):
        self.refresh_from_db()
        res = {'status': 'OK'}
        try:
            self._confirm_deposit(tx, crypto=crypto)
        except Exception as e:
            res = {'status': 'ERROR', 'message': '{}'.format(e)}
        self.save()
        return res

    @transition(field=status, source=PAID, target=PRE_RELEASE)
    def _pre_release(self, api=None):
        healthy = api.health_check(self.pair.base)
        if not healthy:
            raise ValidationError(_(
                'Wallet {} isin\'t working before the release'.format(
                    self.pair.base.wallet
                )
            ))
        return healthy

    def pre_release(self, api=None):
        self.refresh_from_db()
        res = {'status': 'OK'}
        try:
            self._pre_release(api=api)
        except Exception as e:
            res = {'status': 'ERROR', 'message': '{}'.format(e)}
        self.save()
        return res

    @transition(field=status, source=PRE_RELEASE, target=RELEASED)
    def _release(self, tx_data, api=None, currency=None, amount=None):
        if currency != self.pair.base or amount != self.amount_base:
            raise ValidationError(
                'Wrong amount {} or currency {} for order {} release'.format(
                    amount, currency, self.unique_reference
                )
            )
        old_withdraw_txs = self.transactions.exclude(
            type__in=[Transaction.DEPOSIT, Transaction.INTERNAL])
        tx_type = tx_data.get('type')
        if tx_type != Transaction.WITHDRAW:
            msg = 'Bad Transaction type'
            raise ValidationError(msg)
        if len(old_withdraw_txs) == 0:
            tx = Transaction(**tx_data)
            tx.save()

            payment_id = self.payment_id \
                if self.payment_id is not None \
                else None
            destination_tag = self.destination_tag if \
                self.destination_tag is not None \
                else None
            api.assert_tx_unique(currency, self.withdraw_address, amount,
                                 payment_id=payment_id,
                                 destination_tag=destination_tag)
            tx_id, success = api.release_coins(currency, self.withdraw_address,
                                               amount, payment_id=payment_id,
                                               destination_tag=destination_tag)
            setattr(tx, api.TX_ID_FIELD_NAME, tx_id)
            tx.save()
        else:
            msg = 'Order {} already has WITHDRAW or None type ' \
                  'transactions {}'.format(self, old_withdraw_txs)
            self.flag(val=msg)
            raise ValidationError(msg)

        if not tx_id:
            msg = 'Payment release returned None, order {}'.format(self)
            self.flag(val=msg)
            raise ValidationError(msg)

        if success:
            tx.is_verified = True
            tx.save()
        else:
            app.send_task(self.RETRY_RELEASE, [tx.pk],
                          countdown=settings.RETRY_RELEASE_TIME)

        self.set_as_released_on = timezone.now()
        return tx

    def release(self, tx_data, api=None):
        self.refresh_from_db()
        res = {'status': 'OK'}
        try:
            currency = tx_data.get('currency')
            amount = tx_data.get('amount')
            tx = self._release(tx_data, api=api, currency=currency,
                               amount=amount)
            res.update({'tx': tx})
        except Exception as e:
            msg = '{}'.format(e)
            res = {'status': 'ERROR', 'message': msg}
            self.flag(val=msg)
        self.save()
        return res

    @transition(field=status, source=RELEASED, target=COMPLETED)
    def _complete(self, tx):
        if tx.type != Transaction.WITHDRAW:
            raise ValidationError(
                'Order {}. Cannot confirm WITHDRAW - wrong transaction '
                'type'.format(self))
        if tx.is_completed:
            raise ValidationError(
                'Order {}.Cannot confirm DEPOSIT - already confirmed.'.format(
                    self))
        tx.is_completed = True
        tx.is_verified = True
        tx.save()

    def complete(self, tx):
        self.refresh_from_db()
        res = {'status': 'OK'}
        try:
            self._complete(tx)
        except Exception as e:
            res = {'status': 'ERROR', 'message': '{}'.format(e)}
        self.save()
        return res

    @transition(field=status, source=PAID_UNCONFIRMED, target=CANCELED)
    @transition(field=status, source=INITIAL, target=CANCELED)
    def _cancel(self):
        pass

    def cancel(self):
        self.refresh_from_db()
        res = {'status': 'OK'}
        try:
            self._cancel()
        except Exception as e:
            msg = '{}'.format(e)
            res = {'status': 'ERROR', 'message': msg}
            self.flag(val=msg)
        self.save()
        return res

    @transition(field=status, source=PRE_RELEASE, target=REFUNDED)
    @transition(field=status, source=PAID, target=REFUNDED)
    def _refund_exchange(self):
        old_withdraw_txs = self.transactions.exclude(
            type__in=[Transaction.DEPOSIT, Transaction.INTERNAL])
        dep_tx = self.transactions.get(type='D')
        dep_tx.flag(val='refunded')
        currency = dep_tx.currency
        amount = dep_tx.amount
        address = self.refund_address
        if currency != self.pair.quote:
            msg = 'DEPOSIT tx currency {} is not the same as order quote {}.' \
                  ' Order {}'.format(dep_tx.currency, self.pair.quote,
                                     self.unique_reference)
            self.flag(val=msg)
            raise ValidationError(msg)
        if amount != self.amount_quote:
            msg = 'DEPOSIT tx amount {} is not the same as order ' \
                  'amount_quote {}. Order {}'.format(amount, self.amount_quote,
                                                     self.unique_reference)
            self.flag(val=msg)
            raise ValidationError(msg)
        if any([not address, address.currency != self.pair.quote,
                address == self.deposit_address]):
            msg = 'Wrong refund address {}.Order {}'.format(
                address, self.unique_reference
            )
            self.flag(val=msg)
            raise ValidationError(msg)
        if len(old_withdraw_txs) == 0 and dep_tx:
            tx = Transaction(
                amount=amount,
                type=Transaction.REFUND,
                currency=currency,
                refunded_transaction=dep_tx,
                address_to=address,
                order=self
            )
            tx.save()
            api = factory.get_api_client(currency.wallet)
            tx_id, success = api.release_coins(currency, address, amount)
            setattr(tx, api.TX_ID_FIELD_NAME, tx_id)
            tx.save()
        else:
            msg = 'Order {} already has WITHDRAW or None type ' \
                  'transactions {}'.format(self, old_withdraw_txs)
            self.flag(val=msg)
            raise ValidationError(msg)

        return tx

    @transition(field=status, source=PAID_UNCONFIRMED, target=REFUNDED)
    def _refund_fiat(self, refund_type='void'):
        payment = self.payment_set.get()
        payment.flag(val=refund_type)
        if not payment.is_success:
            msg = 'Cannot refund order {}. Deposit payment {} is not ' \
                  'successful'.format(self.unique_reference, payment)
            self.flag(val=msg)
            raise ValidationError(msg)
        order = payment.order
        if order != self:
            msg = 'Cannot refund order {}. Payment order {} is not the ' \
                  'same'.format(self.unique_reference, order)
            self.flag(val=msg)
            raise ValidationError(msg)
        params = {
            'currency': payment.currency.code,
            'amount': str(money_format(payment.amount_cash, places=2)),
            'clientRequestId': '{}_{}'.format(refund_type,
                                              order.unique_reference),
            'clientUniqueId': '{}_{}'.format(refund_type,
                                             order.unique_reference),
            'relatedTransactionId': payment.secondary_payment_system_id,
            'authCode': payment.auth_code
        }
        if refund_type == 'refund':
            res = safe_charge_client.api.refundTransaction(**params)
        elif refund_type == 'void':
            res = safe_charge_client.api.voidTransaction(**params)
        status = res.get('transactionStatus', '')
        if status != 'APPROVED':
            msg = 'Cannot refund order {}. Attemp is not approved: {}'.format(
                self.unique_reference, res
            )
            self.flag(val=msg)
            raise ValidationError(msg)
        payment.is_success = False
        payment.save()
        payment.flag(val=res)

    def refund(self, refund_type='void'):
        self.refresh_from_db()
        res = {'status': 'OK'}
        try:
            if self.exchange:
                tx = self._refund_exchange()
                res.update({'tx': tx})
            else:
                self._refund_fiat(refund_type=refund_type)
        except Exception as e:
            res = {'status': 'ERROR', 'message': '{}'.format(e)}
        self.save()
        return res

    @property
    def amount_quote_fee(self):
        if not self.payment_preference:
            return Decimal('0.0')
        fee = self.payment_preference.payment_method.fee_deposit
        return fee * self.amount_quote

    @property
    def token(self):
        order_count = self.user.orders.all().count()
        if order_count > 1:
            return
        try:
            token = AccessToken.objects.filter(user=self.user).latest('id')
        except AccessToken.DoesNotExist:
            return
        if not token or not token.is_valid():
            return
        return token.token

    @property
    def display_refund_address(self):
        if self.status == self.PRE_RELEASE:
            return True
        if self.status == self.PAID:
            if self.set_as_paid_on \
                    and self.set_as_paid_on + \
                    settings.MAX_TIME_TO_RELEASE_INTERVAL < timezone.now():
                return True
        return False

    @property
    def base_destination_tag(self):
        if self.pair.base.code == 'XRP':
            return self.destination_tag

    @property
    def quote_destination_tag(self):
        if self.pair.quote.code == 'XRP':
            return self.destination_tag

    @property
    def base_payment_id(self):
        if self.pair.base.code == 'XMR':
            return self.payment_id

    @property
    def quote_payment_id(self):
        if self.pair.quote.code == 'XMR':
            return self.payment_id

    @property
    def _referred_with(self):
        try:
            return self.user.profile.referred_with
        except ObjectDoesNotExist:
            pass

    @property
    def fees(self):
        return self.orderfee_set.all()

    @property
    def payment_to_release_time(self):
        if self.set_as_paid_unconfirmed_on and self.set_as_released_on:
            return self.set_as_released_on - self.set_as_paid_unconfirmed_on

    @property
    def create_to_release_time(self):
        if self.created_on and self.set_as_released_on:
            return self.set_as_released_on - self.created_on

    @property
    def is_released_fast(self):
        if self.payment_to_release_time:
            tot_pay_release = self.payment_to_release_time.total_seconds()
            fast_pay_release = \
                tot_pay_release < settings.FAST_PAYMENT_TO_RELEASE_TIME_SECONDS
            tot_create_release = self.create_to_release_time.total_seconds()
            fast_create_release = tot_create_release < settings.\
                FAST_CREATE_TO_RELEASE_TIME_SECONDS
            return fast_create_release and fast_pay_release
        return False

    @property
    def suggest_trustpilot(self):
        user_orders = self.user.orders.all()
        if self.is_released_fast and not user_orders.filter(
                status__in=self.REFUNDABLE_STATES + [self.REFUNDED]):
            for _order in user_orders.filter(
                    status__in=self.IN_SUCCESS_RELEASED):
                if not _order.is_released_fast:
                    return False
            return True
        return False

    @property
    def amount_quote_minus_fees(self):
        tot_fees = self.orderfee_set.all().aggregate(
            models.Sum('amount_quote')
        ).get('amount_quote__sum')
        tot_fees = tot_fees if tot_fees else Decimal('0')
        return self.amount_quote - tot_fees


from pathlib import Path
import sys

import openpyxl
import pandas as pd
import pytest

try:
    from ..RynekPierwotny import Raport, localisation
except:
    sys.path.append(str(Path(sys.path[0]).parent))
    from RynekPierwotny import Raport, localisation

def test_raport_read_excel(tmpdir):
    df = pd.DataFrame({"Mieszkanie1": [30, "A"], "Mieszkanie2": [40, "B"]})
    fake_file_path = Path(tmpdir / "test_file.xlsx")
    df.to_excel(fake_file_path, index=False)
    function_result = Raport.zad_1(fake_file_path)
    assert df.equals(function_result)

def test_raport_read_excel_raises_exception():
    with pytest.raises(Exception):
        fake_file_path = Path()
        function_result = Raport.zad_1(fake_file_path)
        assert function_result

@pytest.mark.skip(
    reason="""If you want to test this function you need to append your API key,
than you are free to take of the skip fixture"""
)
def test_localisation():
    assert localisation("Marszałkowska 10") == (52.22, 21.02)