
import asyncio
import logging
from urllib.parse import quote

import aiohttp
import urllib3
from aiohttp import BasicAuth

urllib3.disable_warnings()

logger = logging.getLogger(__name__)


class JiraException(Exception):
    pass


class Jira(object):
    def __init__(
        self,
        url,
        username,
        ticket_queue,
        password=None,
        token=None,
        loop=None,
    ):
        try:
            logger.debug(":Initializing Jira object:")
            self.url = url
            self.username = username
            self.ticket_queue = ticket_queue
            self.password = password
            self.token = token
            self._auth = None
            if not loop:
                self.loop = asyncio.new_event_loop()
                self.new_loop = True
            else:
                self.loop = loop
                self.new_loop = False

            # Jira Cloud API tokens use HTTP Basic auth (email + token), same as curl -u user:token
            if self.token:
                if not self.username:
                    logger.error(
                        "Basic Authentication with API token requires JIRA_USERNAME (email)"
                    )
                    raise JiraException(
                        "Basic Authentication with API token requires JIRA_USERNAME (email)"
                    )
                self._auth = BasicAuth(self.username, self.token)
            elif self.password:
                if not self.username:
                    logger.error(
                        "Basic Authentication requires JIRA_USERNAME"
                    )
                    raise JiraException(
                        "Basic Authentication requires JIRA_USERNAME"
                    )
                self._auth = BasicAuth(self.username, self.password)
            else:
                logger.error(
                    "Basic Authentication expected: set JIRA_TOKEN or JIRA_PASSWORD"
                )
                raise JiraException(
                    "Basic Authentication expected: set JIRA_TOKEN or JIRA_PASSWORD"
                )
            self.headers = {"Accept": "application/json"}
        except JiraException:
            raise
        except Exception as ex:
            logger.error("Failed to initialize Jira client: %s", ex)
            raise JiraException from ex

    def __exit__(self):
        if self.new_loop:
            self.loop.close()

    async def get_request(self, endpoint):
        logger.debug("GET: %s" % endpoint)
        try:
            async with aiohttp.ClientSession(
                auth=self._auth,
                headers=self.headers,
                loop=self.loop,
            ) as session:
                async with session.get(
                    self.url + endpoint,
                    verify_ssl=False,
                ) as response:
                    result = await response.json(content_type="application/json")
        except Exception as ex:
            logger.debug(ex)
            logger.error("There was something wrong with your request.")
            return None
        if response.status == 404:
            logger.error("Resource not found: %s" % self.url + endpoint)
            return None
        return result

    async def post_request(self, endpoint, payload):
        logger.debug("POST: {%s:%s}" % (endpoint, payload))
        try:
            async with aiohttp.ClientSession(
                auth=self._auth,
                headers=self.headers,
                loop=self.loop,
            ) as session:
                async with session.post(
                    self.url + endpoint,
                    json=payload,
                    verify_ssl=False,
                ) as response:
                    if response.status == 204:
                        data = {}
                    else:
                        data = await response.json(content_type="application/json")
        except Exception as ex:
            logger.debug(ex)
            logger.error("There was something wrong with your request.")
            return False
        if response.status in [200, 201, 204]:
            logger.info("Post successful.")
            return data
        if response.status == 404:
            logger.error("Resource not found: %s" % self.url + endpoint)
        else:
            logger.error(data)
        return False

    async def put_request(self, endpoint, payload):
        logger.debug("POST: {%s:%s}" % (endpoint, payload))
        try:
            async with aiohttp.ClientSession(
                auth=self._auth,
                headers=self.headers,
                loop=self.loop,
            ) as session:
                async with session.put(
                    self.url + endpoint,
                    json=payload,
                    verify_ssl=False,
                ) as response:
                    await response.json(content_type="application/json")
        except Exception as ex:
            logger.debug(ex)
            logger.error("There was something wrong with your request.")
            return False
        if response.status in [200, 201, 204]:
            logger.info("Post successful.")
            return True
        if response.status == 404:
            logger.error("Resource not found: %s" % self.url + endpoint)
        return False

    async def create_ticket(self, summary, description):
        endpoint = "/issue/"
        logger.debug("POST new ticket")
        short_summary = summary.split('\r')
        title = f"{short_summary[0]}"

        data = {
            "fields": {
                "project": {"key": self.ticket_queue},
                "issuetype": {"name": "Task"},
                "summary": title,
                "description": description,
            }
        }
        response = await self.post_request(endpoint, data)
        return response

    async def create_subtask(self, parent_ticket, cloud, description, type_of_subtask):
        endpoint = "/issue/"
        logger.debug("POST new subtask")
        title = f"{cloud} {type_of_subtask}"

        data = {
            "fields": {
                "project": {"key": self.ticket_queue},
                "issuetype": {"id": "5"},
                "parent": {"key": f"{self.ticket_queue}-{parent_ticket}"},
                "summary": title,
                "labels": [type_of_subtask.upper()],
                "description": description,
            }
        }
        response = await self.post_request(endpoint, data)
        return response

    async def _resolve_watcher_to_account_id(self, watcher):
        """
        Jira Cloud expects an Atlassian accountId in the watchers POST body (JSON string),
        not an email. Resolve email or short username via REST v2 user search.
        """
        if not watcher or not str(watcher).strip():
            return None
        w = str(watcher).strip()
        user = await self.get_user_by_email(w)
        if user and user.get("accountId"):
            return user["accountId"]
        if "@" not in w:
            user = await self.get_user_by_email(f"{w}@redhat.com")
            if user and user.get("accountId"):
                return user["accountId"]
        logger.error("Could not resolve Jira accountId for watcher: %s", w)
        return None

    async def add_watcher(self, ticket, watcher):
        account_id = await self._resolve_watcher_to_account_id(watcher)
        if not account_id:
            return False
        issue_id = "%s-%s" % (self.ticket_queue, ticket)
        endpoint = "/issue/%s/watchers" % issue_id
        logger.debug(
            "POST watcher issue=%s accountId=%s (from %s)",
            issue_id,
            account_id,
            watcher,
        )
        response = await self.post_request(endpoint, account_id)
        return response

    async def add_label(self, ticket, label):
        issue_id = "%s-%s" % (self.ticket_queue, ticket)
        endpoint = "/issue/%s" % issue_id
        data = {"update": {"labels": [{"add": label}]}}
        response = await self.put_request(endpoint, data)
        return response

    async def post_comment(self, ticket, comment):
        issue_id = "%s-%s" % (self.ticket_queue, ticket)
        endpoint = "/issue/%s/comment" % issue_id
        payload = {"body": comment}
        response = await self.post_request(endpoint, payload)
        return response

    async def post_transition(self, ticket, transition):
        issue_id = "%s-%s" % (self.ticket_queue, ticket)
        endpoint = "/issue/%s/transitions" % issue_id
        payload = {"transition": {"id": transition}}
        response = await self.post_request(endpoint, payload)
        return response

    async def get_transitions(self, ticket):
        issue_id = "%s-%s" % (self.ticket_queue, ticket)
        endpoint = "/issue/%s/transitions" % issue_id
        result = await self.get_request(endpoint)
        if not result:
            logger.error("Failed to get transitions")
            return []

        transitions = result.get("transitions")
        if transitions:
            return transitions
        else:
            logger.error("No transitions found under %s" % issue_id)
            return []

    async def get_ticket(self, ticket):
        issue_id = "%s-%s" % (self.ticket_queue, ticket)
        endpoint = "/issue/%s" % issue_id
        result = await self.get_request(endpoint)
        if not result:
            logger.error("Failed to get ticket")
            return None
        return result

    async def get_watchers(self, ticket):
        issue_id = "%s-%s" % (self.ticket_queue, ticket)
        endpoint = "/issue/%s/watchers" % issue_id
        logger.debug("GET watchers: %s" % endpoint)
        result = await self.get_request(endpoint)
        if not result:
            logger.error("Failed to get watchers")
            return None
        return result

    async def get_user_by_email(self, email):
        endpoint = f"/user/search?query={quote(email)}"
        logger.debug("GET user: %s" % endpoint)
        result = await self.get_request(endpoint)
        if not result:
            logger.error("User not found for query: %s", email)
            return None
        el = email.lower()
        for user in result:
            if (user.get("emailAddress") or "").lower() == el:
                return user
        if len(result) == 1:
            return result[0]
        return None

    async def get_pending_tickets(self):
        query = {"statusCategory": 4, "labels": "EXTENSION"}
        logger.debug("GET pending tickets")
        result = await self.search_tickets(query)
        if not result:
            logger.error("Failed to get pending tickets")
            return None
        return result

    async def search_tickets(self, query=None):
        project = {"project": self.ticket_queue}
        prefix = "/search?jql="
        query_items = []

        if not query:
            query = project
        else:
            query.update(project)

        for k, v in query.items():
            query_items.append(f"{k}={v}")

        jql = " AND ".join(query_items)
        endpoint = f"{prefix}{jql}"
        logger.debug("GET pending tickets: %s" % endpoint)
        result = await self.get_request(endpoint)
        if not result:
            logger.error("Failed to get pending tickets")
            return None
        return result
