"""An API Client to interact with Agent."""
import logging
from typing import List, Optional
from urllib.parse import urljoin
from .exceptions import AgentConnectionError, AgentError

import aiohttp
import socket
import asyncio
import async_timeout

from agent.device import Device
from datetime import datetime

_LOGGER = logging.getLogger(__name__)


class Agent:
	"""The Agent DVR API client. Create one of these to begin."""

	DEFAULT_SERVER_PATH = '/'
	DEFAULT_TIMEOUT = 10
	OBJECTS_URL = 'command.cgi?cmd=getObjects'

	def __init__(self, server_host, session: aiohttp.client.ClientSession = None) -> None:
		"""Create an Agent API Client."""
		self._server_url = Agent._build_server_url(server_host)
		self._session = session or aiohttp.ClientSession()
		self._close_session = False
		if session is None:
			self._close_session = True
		self._conFailed = False 
		self.disconnected = datetime.now()
		self.reconnect_interval = 10
		self.profiles = []
		self.locations = []
		self.devices = []
		self._raw_result = {}
		return None

	async def close(self) -> None:
		if self._session and self._close_session:
			await self._session.close()

	async def get_state(self, api_url)  -> Optional[dict]:
		"""Perform a GET request on the specified Agent API URL."""
		return await self._agent_request('get', api_url)

	async def _agent_request(self, method, api_url,timeout=DEFAULT_TIMEOUT) -> dict:
		if self._conFailed:
			if (datetime.now()-self.disconnected).total_seconds() < self.reconnect_interval:
				return None
		headers = {
			"User-Agent": f"PythonAgentDVR/",
			"Accept": "application/json, text/plain, */*",
		}

		try:
			with async_timeout.timeout(timeout):
				response = await self._session.request(
					"GET", urljoin(self._server_url, api_url), json=None, headers=headers,
				)
				response.raise_for_status()
		except asyncio.TimeoutError as exception:
			self.disconnected = datetime.now()
			self._conFailed = True
			
			raise AgentConnectionError(
				"Timeout occurred while connecting to Agent DVR server"
			) from exception
		except (
			aiohttp.ClientError,
			aiohttp.ClientResponseError,
			socket.gaierror,
		) as exception:
			self.disconnected = datetime.now()
			self._conFailed = True
			
			raise AgentConnectionError(
				"Error occurred while communicating with Agent DVR server"
			) from exception

		content_type = response.headers.get("Content-Type", "")
		if "application/json" not in content_type:
			self.disconnected = datetime.now()
			self._conFailed = True

			text = await response.text()
			raise AgentError(
				"Unexpected response from the Agent DVR server",
				{"Content-Type": content_type, "response": text},
			)

		json = await response.json()
		if json.get('error') is not None:
			#error from Agent - Server is still available
			raise AgentError(
				"Error from the Agent DVR server",
				{"Content-Type": content_type, "response": json},
			)
		self._conFailed = False
		return json

	async def get_devices(self) -> List[Device]:
		"""Get a list of devices from the Agent API."""
		raw_objects = await self._agent_request('get', Agent.OBJECTS_URL)
		if raw_objects is not None:
			self.devices = []
			self.locations = raw_objects['locations'];
			for raw_result in raw_objects['objectList']:
				self.devices.append(Device(self, raw_result))
		return self.devices
	
	async def get_profiles(self) -> Optional[dict]:
		"""Get a list of Profiles from the Agent API."""
		raw_profiles = await self.get_state('command.cgi?cmd=getProfiles')
		if raw_profiles is not None:
			self.profiles = raw_profiles['profiles']

		return self.profiles

	async def get_active_profile(self) -> Optional[str]:
		"""Get the name of the active profile from the Agent API."""
		profiles = await self.get_profiles()
		if profiles is None:
			return None

		for profile in profiles:
			if profile['active']:
				return profile['name']
		return None

	async def set_active_profile(self, profile_name):
		"""Set the Agent profile to the given id."""
		await self.get_state('command.cgi?cmd=setProfileByName&name={0}'.format(profile_name))

	async def arm(self):
		"""Arm Agent."""
		await self.get_state('command.cgi?cmd=arm')

	async def disarm(self):
		"""Disarm Agent."""
		await self.get_state('command.cgi?cmd=disarm')

	async def update(self):
		state = await self.get_state('command.cgi?cmd=getStatus')
		if state is not None:
			self._raw_result = state

	@property
	def is_available(self) -> bool:
		"""Indicate if this Agent service is currently available."""
		return not self._conFailed

	@property
	def name(self) -> str:
		"""Return name of the server."""
		return self._raw_result["name"]

	@property
	def unique(self) -> str:
		"""Return unique identifier for the server."""
		return self._raw_result["unique"]

	@property
	def remote_access(self) -> bool:
		"""Return unique identifier for the server."""
		return self._raw_result["remoteAccess"]

	@property
	def version(self) -> str:
		"""Return version of the server."""
		return self._raw_result["version"]

	@property
	def raw_result(self) -> dict:
		return self._raw_result

	@property 
	def device_count(self) -> int:
		return self._raw_result["devices"]

	@property
	def is_armed(self) -> bool:
		"""Indicate if Agent is armed."""
		return self._raw_result["armed"]

	@staticmethod
	def _build_server_url(server_host) -> str:
		"""Build the server url making sure it ends in a trailing slash."""
		if server_host[-1] == '/':
			return server_host
		return '{}/'.format(server_host)
