# -*- coding: utf-8 -*-
# Copyright (c) 2020, JINGROW
# For license information, please see license.txt

from datetime import datetime
from typing import TYPE_CHECKING, List, Optional

import jingrow
import requests
from jingrow.model.page import Page
from jingrow.model.naming import make_autoname
from jcloud.api.github import get_access_token, get_auth_headers
from jcloud.overrides import get_permission_query_conditions_for_pagetype
from jcloud.utils import get_current_team, log_error

if TYPE_CHECKING:
	from jcloud.jcloud.pagetype.app_release.app_release import AppRelease


class AppSource(Page):
	# begin: auto-generated types
	# This code is auto-generated. Do not modify anything in this block.

	from typing import TYPE_CHECKING

	if TYPE_CHECKING:
		from jcloud.jcloud.pagetype.app_source_version.app_source_version import AppSourceVersion
		from jingrow.types import DF

		app: DF.Link
		app_title: DF.Data
		branch: DF.Data
		enabled: DF.Check
		github_installation_id: DF.Data | None
		jingrow: DF.Check
		last_github_poll_failed: DF.Check
		last_github_response: DF.Code | None
		last_synced: DF.Datetime | None
		public: DF.Check
		repository: DF.Data | None
		repository_owner: DF.Data | None
		repository_url: DF.Data
		team: DF.Link
		uninstalled: DF.Check
		versions: DF.Table[AppSourceVersion]
	# end: auto-generated types

	dashboard_fields = ["repository_owner", "repository", "branch"]

	def autoname(self):
		series = f"SRC-{self.app}-.###"
		self.name = make_autoname(series)

	def after_insert(self):
		self.create_release()

	def on_update(self):
		self.create_release()

	def validate(self):
		self.validate_source_signature()
		self.validate_duplicate_versions()

	def add_version(self, version):
		self.append("versions", {"version": version})
		self.save()

	def validate_source_signature(self):
		# Don't allow multiple sources with same signature
		if jingrow.db.exists(
			"App Source",
			{
				"name": ("!=", self.name),
				"app": self.app,
				"repository_url": self.repository_url,
				"branch": self.branch,
				"team": self.team,
			},
		):
			jingrow.throw(
				f"Already added {(self.repository_url, self.branch)} for {self.app}",
				jingrow.ValidationError,
			)

	def validate_duplicate_versions(self):
		# Don't allow versions to be added multiple times
		versions = set()
		for row in self.versions:
			if row.version in versions:
				jingrow.throw(
					f"Version {row.version} can be added only once", jingrow.ValidationError
				)
			versions.add(row.version)

	def before_save(self):
		# Assumes repository_url looks like http://git.jingrow.com/jingrow/jerp
		self.repository_url = self.repository_url.removesuffix(".git")

		_, self.repository_owner, self.repository = self.repository_url.rsplit("/", 2)
		# self.create_release()

	@jingrow.whitelist()
	def create_release(self, force: bool = False, commit_hash: str | None = None):
		if self.last_github_poll_failed and not force:
			return

		# 将默认值改为"gitea"
		git_service_type = jingrow.db.get_single_value("Jcloud Settings", "git_service_type") or "gitea"
		
		if git_service_type.lower() == "gitea":
			# 使用Gitea专用函数
			_commit_hash, commit_info, ok = self.get_gitea_commit_info(commit_hash)
		else:
			# 使用原始GitHub函数
			_commit_hash, commit_info, ok = self.get_commit_info(commit_hash)

		if not ok:
			return

		try:
			return self._create_release_for_service(_commit_hash, commit_info, git_service_type)
		except Exception:
			log_error("Create Release Error", pg=self)

	def _create_release(self, commit_hash: str, commit_info: dict) -> str:
		releases = jingrow.get_all(
			"App Release",
			{
				"app": self.app,
				"source": self.name,
				"hash": commit_hash,
			},
			pluck="name",
			limit=1,
		)
		if len(releases) > 0:
			# No need to create a new release
			return releases[0]

		return self.create_release_from_commit_info(
			commit_hash,
			commit_info,
		).name

	def create_release_from_commit_info(
		self,
		commit_hash: str,
		commit_info: dict,
	):
		app_release: "AppRelease" = jingrow.get_pg(
			{
				"pagetype": "App Release",
				"app": self.app,
				"source": self.name,
				"hash": commit_hash,
				"team": self.team,
				"message": commit_info.get("message"),
				"author": commit_info.get("author", {}).get("name"),
				"timestamp": get_timestamp_from_commit_info(commit_info),
			}
		).insert(ignore_permissions=True)
		return app_release

	def get_commit_info(self, commit_hash: None | str = None) -> tuple[str, dict, bool]:
		"""
		If `commit_hash` is not provided, `commit_info` is of the latest commit
		on the branch pointed to by `self.hash`.
		"""
		if (response := self.poll_github(commit_hash)).ok:
			self.set_poll_succeeded()
		else:
			self.set_poll_failed(response)
			return ("", {}, False)

		# Will cause recursion of db.save is used
		self.db_update()

		data = response.json()
		if commit_hash:
			return (commit_hash, data.get("commit", {}), True)

		commit_hash = data.get("commit", {}).get("sha", "")
		commit_info = data.get("commit", {}).get("commit", {})
		return (commit_hash, commit_info, True)

	def poll_github(self, commit_hash: None | str = None) -> requests.Response:
		headers = self.get_auth_headers()
		url = f"https://api.github.com/repos/{self.repository_owner}/{self.repository}"

		if commit_hash:
			# page and per_page set to reduce unnecessary diff info
			url = f"{url}/commits/{commit_hash}?page=1&per_page=1"
		else:
			url = f"{url}/branches/{self.branch}"

		return requests.get(url, headers=headers)

	def poll_gitea(self, commit_hash: None | str = None) -> requests.Response:
		"""专门用于Gitea API的请求函数"""
		headers = self.get_auth_headers()
		git_url = jingrow.db.get_single_value("Jcloud Settings", "git_url") or "http://git.jingrow.com"
		url = f"{git_url}/api/v1/repos/{self.repository_owner}/{self.repository}"

		if commit_hash:
			url = f"{url}/commits/{commit_hash}" 
		else:
			url = f"{url}/branches/{self.branch}"

		return requests.get(url, headers=headers)

	def set_poll_succeeded(self):
		self.last_github_response = ""
		self.last_github_poll_failed = False
		self.last_synced = jingrow.utils.now()
		self.uninstalled = False

	def set_poll_failed(self, response):
		self.last_github_response = response.text or ""
		self.last_github_poll_failed = True
		self.last_synced = jingrow.utils.now()

		"""
		If poll fails with 404 after updating the `github_installation_id` it
		*probably* means that FC hasn't been granted access to this particular
		app by the user.

		In this case the App Source is in an uninstalled state.
		"""
		self.uninstalled = response.status_code == 404

		if response.status_code != 404:
			log_error(
				"Create Release Error",
				response_status_code=response.status_code,
				response_text=response.text,
				pg=self,
			)

	def get_auth_headers(self) -> dict:
		settings = jingrow.get_single("Jcloud Settings")		
		git_service_type = settings.get("git_service_type") or "gitea"
		
		if git_service_type.lower() == "gitea":
			git_access_token = settings.get_password("git_access_token")
			if git_access_token:
				return {"Authorization": f"token {git_access_token}"}
		
		if git_service_type.lower() == "github" and self.github_installation_id:
			return get_auth_headers(self.github_installation_id)
		
		return {}

	def get_access_token(self) -> Optional[str]:
		settings = jingrow.get_single("Jcloud Settings")		
		git_service_type = settings.get("git_service_type") or "gitea"
		
		if git_service_type.lower() == "gitea":
			git_access_token = settings.get_password("git_access_token")
			if git_access_token:
				return git_access_token

		if git_service_type.lower() == "github" and self.github_installation_id:
			return get_access_token(self.github_installation_id)

		return settings.get_password("github_access_token")

	def get_repo_url(self) -> str:
		settings = jingrow.get_single("Jcloud Settings")
		if not settings:
			return self.repository_url
		
		git_service_type = settings.get("git_service_type") or "gitea"
		
		if git_service_type.lower() == "gitea":
			git_username = settings.get("git_username")
			git_access_token = settings.get_password("git_access_token")
			if git_username and git_access_token:
				return f"http://{git_username}:{git_access_token}@git.jingrow.com/{self.repository_owner}/{self.repository}"
		
		if git_service_type.lower() == "github" and self.github_installation_id:
			token = get_access_token(self.github_installation_id)
			if token is None:
				# Do not edit without updating deploy_notifications.py
				raise Exception("App installation token could not be fetched", self.app)
			return f"https://x-access-token:{token}@git.jingrow.com/{self.repository_owner}/{self.repository}"
		
		return self.repository_url

	def get_gitea_commit_info(self, commit_hash: None | str = None) -> tuple[str, dict, bool]:
		"""专门处理Gitea API响应的函数"""
		try:
			if (response := self.poll_gitea(commit_hash)).ok:
				self.set_poll_succeeded()
			else:
				self.set_poll_failed(response) 
				return ("", {}, False)

			self.db_update()
			
			data = response.json()
			
			if commit_hash:
				# 单个提交信息
				return (commit_hash, data, True)

			# 从分支信息中获取提交
			commit_data = data.get("commit", {})
			
			# Gitea使用id字段存储哈希
			commit_hash = commit_data.get("id", "")
			
			# 确保commit_hash不为空
			if not commit_hash:
				log_error("获取提交哈希失败", response_data=data, pg=self)
				self.last_github_response = "无法从Gitea API获取有效的提交哈希"
				self.last_github_poll_failed = True
				self.db_update()
				return ("", {}, False)
				
			return (commit_hash, commit_data, True)
			
		except requests.exceptions.RequestException as e:
			log_error("获取Gitea提交信息失败", error=str(e), pg=self)
			self.last_github_poll_failed = True
			self.last_github_response = f"与Gitea服务器通信失败: {str(e)}"
			self.db_update()
			return ("", {}, False)

	def create_release_from_gitea_commit_info(self, commit_hash: str, commit_info: dict):
		"""专门从Gitea提交信息创建Release的函数"""
		message = commit_info.get("message", "")
		
		# Gitea的作者信息结构
		author_info = commit_info.get("author", {})
		author_name = author_info.get("name", "") or author_info.get("username", "")
		
		# Gitea的时间戳字段
		timestamp_str = commit_info.get("timestamp", "")
		timestamp = None
		
		if timestamp_str:
			try:
				timestamp = datetime.fromisoformat(timestamp_str).strftime("%Y-%m-%d %H:%M:%S")
			except (ValueError, TypeError):
				pass
		
		app_release: "AppRelease" = jingrow.get_pg(
			{
				"pagetype": "App Release",
				"app": self.app,
				"source": self.name,
				"hash": commit_hash,
				"team": self.team,
				"message": message,
				"author": author_name,
				"timestamp": timestamp,
			}
		).insert(ignore_permissions=True)
		return app_release

	def _create_release_for_service(self, commit_hash: str, commit_info: dict, service_type: str = "github") -> str:
		"""根据不同Git服务类型创建Release"""
		releases = jingrow.get_all(
			"App Release",
			{
				"app": self.app,
				"source": self.name,
				"hash": commit_hash,
			},
			pluck="name",
			limit=1,
		)
		if len(releases) > 0:
			# 已存在，无需创建
			return releases[0]

		if service_type.lower() == "gitea":
			return self.create_release_from_gitea_commit_info(
				commit_hash,
				commit_info,
			).name
		else:
			return self.create_release_from_commit_info(
				commit_hash,
				commit_info,
			).name


def create_app_source(
	app: str, repository_url: str, branch: str, versions: List[str]
) -> AppSource:
	team = get_current_team()

	app_source = jingrow.get_pg(
		{
			"pagetype": "App Source",
			"app": app,
			"repository_url": repository_url,
			"branch": branch,
			"team": team,
			"versions": [{"version": version} for version in versions],
		}
	)

	app_source.save()

	return app_source


get_permission_query_conditions = get_permission_query_conditions_for_pagetype(
	"App Source"
)


def get_timestamp_from_commit_info(commit_info: dict) -> str | None:
	timestamp_str = commit_info.get("author", {}).get("date")
	if not timestamp_str:
		return None

	timestamp_str = timestamp_str.replace("Z", "+00:00")
	return datetime.fromisoformat(timestamp_str).strftime("%Y-%m-%d %H:%M:%S")
