# Copyright (c) 2020, JINGROW
# For license information, please see license.txt
from __future__ import annotations

import os
from hashlib import blake2b

import jingrow
from jingrow import _
from jingrow.contacts.address_and_contact import load_address_and_contact
from jingrow.core.utils import find
from jingrow.model.page import Page
from jingrow.utils import get_fullname, get_url_to_form, random_string

from jcloud.api.client import dashboard_whitelist
from jcloud.exceptions import JingrowioServerNotSet
from jcloud.jcloud.pagetype.telegram_message.telegram_message import TelegramMessage
from jcloud.utils import get_valid_teams_for_user, has_role, log_error
from jcloud.utils.billing import (
	get_jingrow_io_connection,
	get_stripe,
	process_micro_debit_test_charge,
)
from jcloud.utils.telemetry import capture


class Team(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 jingrow.types import DF
		from jcloud.jcloud.pagetype.child_team_member.child_team_member import ChildTeamMember
		from jcloud.jcloud.pagetype.communication_email.communication_email import CommunicationEmail
		from jcloud.jcloud.pagetype.invoice_discount.invoice_discount import InvoiceDiscount
		from jcloud.jcloud.pagetype.team_member.team_member import TeamMember

		account_request: DF.Link | None
		benches_enabled: DF.Check
		billing_address: DF.Link | None
		billing_email: DF.Data | None
		billing_name: DF.Data | None
		billing_team: DF.Link | None
		child_team_members: DF.Table[ChildTeamMember]
		code_servers_enabled: DF.Check
		communication_emails: DF.Table[CommunicationEmail]
		country: DF.Link | None
		currency: DF.Link | None
		database_access_enabled: DF.Check
		default_payment_method: DF.Link | None
		discounts: DF.Table[InvoiceDiscount]
		enable_inplace_updates: DF.Check
		enable_performance_tuning: DF.Check
		enabled: DF.Check
		enforce_2fa: DF.Check
		jerp_partner: DF.Check
		jingrow_partnership_date: DF.Date | None
		free_account: DF.Check
		free_credits_allocated: DF.Check
		github_access_token: DF.Data | None
		is_code_server_user: DF.Check
		is_developer: DF.Check
		is_pro: DF.Check
		is_saas_user: DF.Check
		is_us_eu: DF.Check
		last_used_team: DF.Link | None
		mpesa_enabled: DF.Check
		mpesa_phone_number: DF.Data | None
		mpesa_tax_id: DF.Data | None
		notify_email: DF.Data | None
		parent_team: DF.Link | None
		partner_commission: DF.Percent
		partner_email: DF.Data | None
		partner_referral_code: DF.Data | None
		partnership_date: DF.Date | None
		payment_mode: DF.Literal["", "Card", "Prepaid Credits", "Paid By Partner"]
		razorpay_enabled: DF.Check
		referrer_id: DF.Data | None
		security_portal_enabled: DF.Check
		self_hosted_servers_enabled: DF.Check
		send_notifications: DF.Check
		servers_enabled: DF.Check
		skip_backups: DF.Check
		ssh_access_enabled: DF.Check
		stripe_customer_id: DF.Data | None
		team_members: DF.Table[TeamMember]
		team_title: DF.Data | None
		user: DF.Link | None
		via_jerp: DF.Check
	# end: auto-generated types

	dashboard_fields = (
		"enabled",
		"team_title",
		"user",
		"partner_email",
		"jerp_partner",
		"enforce_2fa",
		"billing_team",
		"team_members",
		"child_team_members",
		"notify_email",
		"country",
		"currency",
		"payment_mode",
		"default_payment_method",
		"skip_backups",
		"is_saas_user",
		"billing_name",
		"referrer_id",
		"partner_referral_code",
		"parent_team",
		"is_developer",
		"is_pro",
		"enable_performance_tuning",
		"enable_inplace_updates",
		"servers_enabled",
		"mpesa_tax_id",
		"mpesa_phone_number",
		"mpesa_enabled",
		"account_request",
	)

	def get_pg(self, pg):
		if (
			not jingrow.local.system_user()
			and self.user != jingrow.session.user
			and jingrow.session.user not in self.get_user_list()
		):
			jingrow.throw("You are not allowed to access this document")

		user = jingrow.db.get_value(
			"User",
			jingrow.session.user,
			["name", "first_name", "last_name", "user_image", "user_type", "email", "api_key", "username", "mobile_no"],
			as_dict=True,
		)
		user.is_2fa_enabled = jingrow.db.get_value("User 2FA", {"user": user.name}, "enabled")
		pg.user_info = user
		pg.balance = self.get_balance()
		pg.is_desk_user = user.user_type == "System User"
		pg.is_support_agent = has_role("Jcloud Support Agent")
		pg.valid_teams = get_valid_teams_for_user(jingrow.session.user)
		pg.onboarding = self.get_onboarding()
		pg.billing_info = self.billing_info()
		pg.billing_details = self.billing_details()
		pg.trial_sites = self.get_trial_sites()
		pg.pending_site_request = self.get_pending_saas_site_request()
		pg.payment_method = jingrow.db.get_value(
			"Stripe Payment Method",
			{"team": self.name, "name": self.default_payment_method},
			[
				"name",
				"last_4",
				"name_on_card",
				"expiry_month",
				"expiry_year",
				"brand",
				"stripe_mandate_id",
			],
			as_dict=True,
		)

	def onload(self):
		load_address_and_contact(self)

	@jingrow.whitelist()
	def get_home_data(self):
		return {
			"sites": jingrow.db.get_all(
				"Site",
				{"team": self.name, "status": ["!=", "Archived"]},
				["name", "host_name", "status"],
			),
		}

	def validate(self):
		self.validate_duplicate_members()
		self.set_team_currency()
		self.set_default_user()
		self.set_billing_name()
		self.set_partner_email()
		self.validate_disable()
		self.validate_billing_team()

	def before_insert(self):

		self.currency = "CNY" if self.country == "China" else "USD"

		if not self.referrer_id:
			self.set_referrer_id()

	def set_notification_emails(self):
		if not self.notify_email:
			self.notify_email = self.user

		if not self.billing_email:
			self.billing_email = self.user

	def set_referrer_id(self):
		h = blake2b(digest_size=4)
		h.update(self.user.encode())
		self.referrer_id = h.hexdigest()

	def set_partner_email(self):
		if self.jerp_partner and not self.partner_email:
			self.partner_email = self.user

	def validate_disable(self):
		if self.has_value_changed("enabled") and self.enabled == 0 and has_unsettled_invoices(self.name):
			jingrow.throw(
				"Cannot disable team with Draft or Unpaid invoices. Please finalize and settle the pending invoices first"
			)

	def validate_billing_team(self):
		if not (self.billing_team and self.payment_mode == "Paid By Partner"):
			return

		if self.payment_mode == "Paid By Partner" and not self.billing_team:
			jingrow.throw("Billing Team is mandatory for Paid By Partner payment mode")

		if self.payment_mode == "Paid By Partner" and has_unsettled_invoices(self.name):
			jingrow.throw(
				"Cannot set payment mode to Paid By Partner. Please finalize and settle the pending invoices first"
			)

	def delete(self, force=False, workflow=False):
		if not (force or workflow):
			jingrow.throw(
				f"You are only deleting the Team Document for {self.name}. To continue to"
				" do so, pass force=True with this call. Else, pass workflow=True to raise"
				" a Team Deletion Request to trigger complete team deletion process."
			)

		if force:
			return super().delete()

		if workflow:
			return jingrow.get_pg({"pagetype": "Team Deletion Request", "team": self.name}).insert()

		jingrow.throw(
			f"You are only deleting the Team Document for {self.name}. To continue to"
			" do so, pass force=True with this call. Else, pass workflow=True to raise"
			" a Team Deletion Request to trigger complete team deletion process."
		)
		return None

	def disable_account(self):
		self.suspend_sites("Account disabled")
		self.enabled = False
		self.save()
		self.add_comment("Info", "disabled account")

	def enable_account(self):
		self.unsuspend_sites("Account enabled")
		self.enabled = True
		self.save()
		self.add_comment("Info", "enabled account")

	@classmethod
	def create_new(
		cls,
		account_request: AccountRequest,
		first_name: str,
		last_name: str,
		password: str | None = None,
		country: str | None = None,
		is_us_eu: bool = False,
		via_jerp: bool = False,
		user_exists: bool = False,
	):
		"""Create new team along with user (user created first)."""
		team: "Team" = jingrow.get_pg(
			{
				"pagetype": "Team",
				"user": account_request.email,
				"country": country,
				"enabled": 1,
				"via_jerp": via_jerp,
				"is_us_eu": is_us_eu,
				"account_request": account_request.name,
			}
		)

		if not user_exists:
			user = team.create_user(
				first_name, last_name, account_request.email, password, account_request.role
			)
		else:
			user = jingrow.get_pg("User", account_request.email)
			user.append_roles(account_request.role)
			user.save(ignore_permissions=True)

		team.team_title = "Parent Team"
		team.insert(ignore_permissions=True, ignore_links=True)
		team.append("team_members", {"user": user.name})
		if not account_request.invited_by_parent_team:
			team.append("communication_emails", {"type": "invoices", "value": user.name})
			team.append("communication_emails", {"type": "marketplace_notifications", "value": user.name})
		else:
			team.parent_team = account_request.invited_by

		if account_request.product_trial:
			team.is_saas_user = 1

		team.save(ignore_permissions=True)

		team.create_stripe_customer()

		if account_request.referrer_id:
			team.create_referral_bonus(account_request.referrer_id)

		if not team.via_jerp and not account_request.invited_by_parent_team:
			team.create_upcoming_invoice()
		return team

	@staticmethod
	def create_user(first_name=None, last_name=None, email=None, password=None, role=None):
		user = jingrow.new_pg("User")
		user.first_name = first_name
		user.last_name = last_name
		user.email = email
		user.owner = email
		user.new_password = password
		user.append_roles(role)
		user.flags.no_welcome_mail = True
		user.save(ignore_permissions=True)
		return user

	def create_user_for_member(
		self,
		first_name=None,
		last_name=None,
		email=None,
		password=None,
		role=None,
		jcloud_roles=None,
	):
		user = jingrow.db.get_value("User", email, ["name"], as_dict=True)
		if not user:
			user = self.create_user(first_name, last_name, email, password, role)

		self.append("team_members", {"user": user.name})
		self.save(ignore_permissions=True)

		for role in jcloud_roles or []:
			jingrow.get_pg("Jcloud Role", role.jcloud_role).add_user(user.name)

	@dashboard_whitelist()
	def remove_team_member(self, member):
		member_to_remove = find(self.team_members, lambda x: x.user == member)
		if member_to_remove:
			self.remove(member_to_remove)

			JcloudRole = jingrow.qb.PageType("Jcloud Role")
			JcloudRoleUser = jingrow.qb.PageType("Jcloud Role User")
			roles = (
				jingrow.qb.from_(JcloudRole)
				.join(JcloudRoleUser)
				.on(JcloudRole.name == JcloudRoleUser.parent)
				.where(JcloudRoleUser.user == member)
				.select(JcloudRole.name)
				.run(as_dict=True, pluck="name")
			)

			for role in roles:
				jingrow.get_pg("Jcloud Role", role).remove_user(member)
		else:
			jingrow.throw(f"Team member {jingrow.bold(member)} does not exists")

		self.save(ignore_permissions=True)

	def set_billing_name(self):
		if not self.billing_name:
			self.billing_name = jingrow.utils.get_fullname(self.user)

	def set_default_user(self):
		if not self.user and self.team_members:
			self.user = self.team_members[0].user

	def set_team_currency(self):
		if not self.currency and self.country:
			self.currency = "CNY" if self.country == "China" else "USD"

	def get_user_list(self):
		return [row.user for row in self.team_members]

	def get_users_only_in_this_team(self):
		return [
			user
			for user in self.get_user_list()
			if not jingrow.db.exists("Team Member", {"user": user, "parent": ("!=", self.name)})
		]

	def validate_duplicate_members(self):
		team_users = self.get_user_list()
		duplicate_members = [m for m in team_users if team_users.count(m) > 1]
		duplicate_members = list(set(duplicate_members))
		if duplicate_members:
			jingrow.throw(
				_("Duplicate Team Members: {0}").format(", ".join(duplicate_members)),
				jingrow.DuplicateEntryError,
			)

	def validate_payment_mode(self):
		"""简化版的支付方式验证，不再检查余额是否充足"""
		# 如果没有设置支付方式，默认设为余额支付
		if not self.payment_mode:
			self.payment_mode = "Prepaid Credits"
		
		# 只保留对卡支付的基本验证
		if self.has_value_changed("payment_mode") and self.payment_mode == "Card":
			if jingrow.db.count("Stripe Payment Method", {"team": self.name}) == 0:
				jingrow.throw("No card added")

		# 处理默认支付方法的逻辑
		if not self.is_new() and not self.default_payment_method:
			# 重置支付方法的默认状态
			payment_methods = jingrow.db.get_list(
				"Stripe Payment Method", {"team": self.name, "is_default": 1}
			)
			for pm in payment_methods:
				pg = jingrow.get_pg("Stripe Payment Method", pm.name)
				pg.is_default = 0
				pg.save()

		# 保留遥测事件记录
		if self.has_value_changed("payment_mode") and self.payment_mode and self.account_request:
			old_pg = self.get_pg_before_save()
			if (not old_pg) or (not old_pg.payment_mode):
				ar = jingrow.get_pg("Account Request", self.account_request)
				if not (ar.is_saas_signup() or ar.invited_by_parent_team):
					capture("added_card_or_prepaid_credits", "fc_signup", self.user)

	def on_update(self):
		if not self.enabled:
			return

		self.validate_payment_mode()
		self.update_draft_invoice_payment_mode()
		self.validate_partnership_date()


	def validate_partnership_date(self):
		if self.jerp_partner or not self.partnership_date:
			return

		if partner_email := self.partner_email:
			jingrow_partnership_date = jingrow.db.get_value(
				"Team",
				{"enabled": 1, "jerp_partner": 1, "partner_email": partner_email},
				"jingrow_partnership_date",
			)
			if jingrow_partnership_date and jingrow_partnership_date > jingrow.utils.getdate(
				self.partnership_date
			):
				jingrow.throw("Partnership date cannot be less than the partnership date of the partner")

	def update_draft_invoice_payment_mode(self):
		if self.has_value_changed("payment_mode"):
			draft_invoices = jingrow.get_all(
				"Invoice", filters={"pagestatus": 0, "team": self.name}, pluck="name"
			)

			for invoice in draft_invoices:
				jingrow.db.set_value("Invoice", invoice, "payment_mode", self.payment_mode)

	@jingrow.whitelist()
	def impersonate(self, member, reason):
		user = jingrow.db.get_value("Team Member", member, "user")
		impersonation = jingrow.get_pg(
			{
				"pagetype": "Team Member Impersonation",
				"user": user,
				"impersonator": jingrow.session.user,
				"team": self.name,
				"member": member,
				"reason": reason,
			}
		)
		impersonation.save()
		jingrow.local.login_manager.login_as(user)

	@jingrow.whitelist()
	def enable_jerp_partner_privileges(self):
		self.jerp_partner = 1
		if not self.partner_email:
			self.partner_email = self.user
		self.jingrow_partnership_date = self.get_partnership_start_date()
		self.servers_enabled = 1
		self.save(ignore_permissions=True)
		self.create_partner_referral_code()
		self.create_new_invoice()

	@jingrow.whitelist()
	def disable_jerp_partner_privileges(self):
		self.jerp_partner = 0
		self.save(ignore_permissions=True)

	def create_partner_referral_code(self):
		if not self.partner_referral_code:
			self.partner_referral_code = random_string(10).upper()
			self.save(ignore_permissions=True)

	def get_partnership_start_date(self):
		if jingrow.flags.in_test:
			return jingrow.utils.getdate()

		client = get_jingrow_io_connection()
		data = client.get_value("Partner", "start_date", {"email": self.partner_email})
		if not data:
			jingrow.throw("Partner not found on framework.jingrow.com")
		return jingrow.utils.getdate(data.get("start_date"))

	def create_new_invoice(self):
		"""
		After enabling partner privileges, new invoice should be created
		to track the partner achievements
		"""
		# check if any active user with an invoice
		if not jingrow.get_all("Invoice", {"team": self.name, "pagestatus": ("<", 2)}, pluck="name"):
			return
		today = jingrow.utils.getdate()
		current_invoice = jingrow.db.get_value(
			"Invoice",
			{
				"team": self.name,
				"type": "Subscription",
				"pagestatus": 0,
				"period_end": jingrow.utils.get_last_day(today),
			},
			"name",
		)

		if not current_invoice:
			return

		current_inv_pg = jingrow.get_pg("Invoice", current_invoice)

		if current_inv_pg.partner_email and current_inv_pg.partner_email == self.partner_email:
			# don't create new invoice if partner email is set
			return

		if (
			not current_invoice
			or today == jingrow.utils.get_last_day(today)
			or today == current_inv_pg.period_start
		):
			# don't create invoice if new team or today is the last day of the month
			return
		current_inv_pg.period_end = jingrow.utils.add_days(today, -1)
		current_inv_pg.flags.on_partner_conversion = True
		current_inv_pg.save()
		current_inv_pg.finalize_invoice()

		# create invoice
		invoice = jingrow.get_pg(
			{
				"pagetype": "Invoice",
				"team": self.name,
				"type": "Subscription",
				"period_start": today,
			}
		)
		invoice.insert()

	def create_referral_bonus(self, referrer_id):
		# Get team name with this this referrer id
		referrer_team = jingrow.db.get_value("Team", {"referrer_id": referrer_id})
		jingrow.get_pg(
			{"pagetype": "Referral Bonus", "for_team": self.name, "referred_by": referrer_team}
		).insert(ignore_permissions=True)

	def has_member(self, user):
		return user in self.get_user_list()

	def is_defaulter(self):
		if self.free_account:
			return False

		try:
			unpaid_invoices = jingrow.get_all(
				"Invoice",
				{
					"status": "Unpaid",
					"team": self.name,
					"pagestatus": ("<", 2),
					"type": "Subscription",
				},
				pluck="name",
			)
		except jingrow.DoesNotExistError:
			return False

		return unpaid_invoices

	def create_stripe_customer(self):
		if not self.stripe_customer_id:
			stripe = get_stripe()
			customer = stripe.Customer.create(email=self.user, name=get_fullname(self.user))
			self.stripe_customer_id = customer.id
			self.save()

	@jingrow.whitelist()
	def update_billing_details(self, billing_details):
		if self.billing_address:
			address_pg = jingrow.get_pg("Address", self.billing_address)
			if (address_pg.country != billing_details.country) and (
				address_pg.country == "China" or billing_details.country == "China"
			):
				jingrow.throw("Cannot change country of billing address")
		else:
			if self.account_request:
				ar: "AccountRequest" = jingrow.get_pg("Account Request", self.account_request)
				if not (ar.is_saas_signup() or ar.invited_by_parent_team):
					capture("added_billing_address", "fc_signup", self.user)
			address_pg = jingrow.new_pg("Address")
			address_pg.address_title = billing_details.billing_name or self.billing_name
			address_pg.append(
				"links",
				{"link_pagetype": self.pagetype, "link_name": self.name, "link_title": self.name},
			)

		address_pg.update(
			{
				"address_line1": billing_details.address,
				"city": billing_details.city,
				"state": billing_details.state,
				"pincode": billing_details.get("postal_code", "").strip().replace(" ", ""),
				"country": billing_details.country,
				"gstin": billing_details.gstin,
			}
		)
		address_pg.save()
		address_pg.reload()

		self.billing_name = billing_details.billing_name or self.billing_name
		self.billing_address = address_pg.name
		self.save()
		self.reload()

		self.update_billing_details_on_draft_invoices()

	def update_billing_details_on_draft_invoices(self):
		draft_invoices = jingrow.get_all("Invoice", {"team": self.name, "pagestatus": 0}, pluck="name")
		for draft_invoice in draft_invoices:
			# Invoice.customer_name set by Invoice.validate()
			jingrow.get_pg("Invoice", draft_invoice).save()

	def create_payment_method(
		self,
		payment_method_id,
		setup_intent_id,
		mandate_id,
		mandate_reference,
		set_default=False,
	):
		stripe = get_stripe()
		payment_method = stripe.PaymentMethod.retrieve(payment_method_id)

		pg = jingrow.get_pg(
			{
				"pagetype": "Stripe Payment Method",
				"stripe_payment_method_id": payment_method["id"],
				"last_4": payment_method["card"]["last4"],
				"name_on_card": payment_method["billing_details"]["name"],
				"expiry_month": payment_method["card"]["exp_month"],
				"expiry_year": payment_method["card"]["exp_year"],
				"brand": payment_method["card"]["brand"] or "",
				"team": self.name,
				"stripe_setup_intent_id": setup_intent_id,
				"stripe_mandate_id": mandate_id if mandate_id else None,
				"stripe_mandate_reference": mandate_reference if mandate_reference else None,
			}
		)
		pg.insert()

		# unsuspend sites on payment method added
		self.unsuspend_sites(reason="Payment method added")
		if set_default:
			pg.set_default()
			self.reload()

		self.remove_subscription_config_in_trial_sites()

		return pg

	def get_payment_methods(self):
		return jingrow.db.get_all(
			"Stripe Payment Method",
			{"team": self.name},
			[
				"name",
				"last_4",
				"name_on_card",
				"expiry_month",
				"expiry_year",
				"brand",
				"is_default",
				"creation",
			],
			order_by="creation desc",
		)

	def get_past_invoices(self):
		invoices = jingrow.db.get_all(
			"Invoice",
			filters={
				"team": self.name,
				"status": ("not in", ("Draft", "Refunded")),
				"pagestatus": ("!=", 2),
			},
			fields=[
				"name",
				"total",
				"amount_due",
				"status",
				"type",
				"stripe_invoice_url",
				"period_start",
				"period_end",
				"due_date",
				"payment_date",
				"currency",
				"invoice_pdf",
				"due_date as date",
			],
			order_by="due_date desc",
		)

		for invoice in invoices:
			invoice.formatted_total = jingrow.utils.fmt_money(invoice.total, 2, invoice.currency)
			invoice.stripe_link_expired = False
			if invoice.status == "Unpaid":
				invoice.formatted_amount_due = jingrow.utils.fmt_money(invoice.amount_due, 2, invoice.currency)
				days_diff = jingrow.utils.date_diff(jingrow.utils.now(), invoice.due_date)
				if days_diff > 30:
					invoice.stripe_link_expired = True
		return invoices

	def allocate_credit_amount(self, amount, source, remark=None, type="Adjustment"):
		pg = jingrow.get_pg(
			pagetype="Balance Transaction",
			team=self.name,
			type=type,
			source=source,
			amount=amount,
			description=remark,
		)
		pg.insert(ignore_permissions=True)
		pg.submit()

		self.reload()
		if not self.payment_mode:
			self.validate_payment_mode()
			self.save(ignore_permissions=True)
		return pg

	def get_available_credits(self):
		def get_stripe_balance():
			return self.get_stripe_balance()

		return jingrow.cache().hget("customer_available_credits", self.name, generator=get_stripe_balance)

	def get_stripe_balance(self):
		stripe = get_stripe()
		customer_object = stripe.Customer.retrieve(self.stripe_customer_id)
		return (customer_object["balance"] * -1) / 100

	@dashboard_whitelist()
	def get_team_members(self):
		return get_team_members(self.name)

	@dashboard_whitelist()
	def invite_team_member(self, username, roles=None):
		JcloudRole = jingrow.qb.PageType("Jcloud Role")
		JcloudRoleUser = jingrow.qb.PageType("Jcloud Role User")

		has_admin_access = (
			jingrow.qb.from_(JcloudRole)
			.select(JcloudRole.name)
			.join(JcloudRoleUser)
			.on((JcloudRole.name == JcloudRoleUser.parent) & (JcloudRoleUser.user == jingrow.session.user))
			.where(JcloudRole.team == self.name)
			.where(JcloudRole.admin_access == 1)
		)

		if jingrow.session.user != self.user and not has_admin_access.run():
			jingrow.throw(_("Only team owner can invite team members"))

		# 验证用户名是否存在
		if not username:
			jingrow.throw(_("用户名不能为空"))
		
		# 检查用户是否存在（通过 username 或 name）
		user = jingrow.db.get_value("User", {"username": username}, ["name", "enabled"], as_dict=True)
		if not user:
			# 如果通过 username 找不到，尝试通过 name（因为 name 可能是 username）
			user = jingrow.db.get_value("User", username, ["name", "enabled"], as_dict=True)
		
		if not user:
			jingrow.throw(_("用户不存在"))
		
		if not user.enabled:
			jingrow.throw(_("用户已被禁用"))

		# 检查是否已经是团队成员
		if jingrow.db.exists("Team Member", {"user": user.name, "parent": self.name, "parenttype": "Team"}):
			jingrow.throw(_("该用户已经是团队成员"))

		# 直接添加团队成员
		self.append("team_members", {"user": user.name})
		self.save(ignore_permissions=True)

		# 添加角色
		if roles:
			for role in roles:
				role_pg = jingrow.get_pg("Jcloud Role", role)
				if role_pg:
					role_pg.add_user(user.name)

	@jingrow.whitelist()
	def get_balance(self):
		res = jingrow.get_all(
			"Balance Transaction",
			filters={"team": self.name, "pagestatus": 1, "type": ("!=", "Partnership Fee")},
			order_by="creation desc",
			limit=1,
			pluck="ending_balance",
		)
		if not res:
			return 0
		return res[0]

	def can_create_site(self):  # noqa: C901
		why = ""
		allow = (True, "")

		if not self.enabled:
			why = "You cannot create a new site because your account is disabled"
			return (False, why)

		if self.free_account or self.parent_team or self.billing_team:
			return allow

		if self.is_saas_user and not self.payment_mode:
			if not jingrow.db.get_all("Site", {"team": self.name}, limit=1):
				return allow
			why = "You have already created trial site in the past"

		# allow user to create their first site without payment method
		if not jingrow.db.get_all("Site", {"team": self.name}, limit=1):
			return allow


		if self.payment_mode == "Prepaid Credits":
			# if balance is greater than 0 or have atleast 2 paid invoices, then allow to create site
			if (
				self.get_balance() > 0
				or jingrow.db.count(
					"Invoice",
					{
						"team": self.name,
						"status": "Paid",
						"amount_paid": ("!=", 0),
					},
				)
				> 2
			):
				return allow
			why = "Cannot create site due to insufficient balance"

		if self.payment_mode == "Card":
			if self.default_payment_method:
				return allow
			why = "Cannot create site without adding a card"

		return (False, why)

	def can_install_paid_apps(self):
		if self.free_account or self.billing_team or self.payment_mode:
			return True

		return bool(
			jingrow.db.exists("Invoice", {"team": self.name, "amount_paid": (">", 0), "status": "Paid"})
		)

	def billing_info(self):
		"""
		返回账单信息
		注意：之前此方法依赖Stripe，现已简化
		"""
		return {
			"name": self.billing_name or jingrow.utils.get_fullname(self.user),
			"email": self.notify_email or self.user,
			"address": jingrow.db.get_value("Address", self.billing_address, "address_line1") if self.billing_address else "",
			"city": jingrow.db.get_value("Address", self.billing_address, "city") if self.billing_address else "",
			"state": jingrow.db.get_value("Address", self.billing_address, "state") if self.billing_address else "",
			"country": self.country or "",
			"postal_code": jingrow.db.get_value("Address", self.billing_address, "pincode") if self.billing_address else "",
			"gstin": jingrow.db.get_value("Address", self.billing_address, "gstin") if self.billing_address else ""
		}

	def billing_details(self):
		"""
		返回账单详情
		注意：之前此方法依赖Stripe，现已简化
		"""
		address = {}
		if self.billing_address:
			address = jingrow.db.get_value(
				"Address",
				self.billing_address,
				["address_line1", "city", "state", "country", "pincode", "gstin"],
				as_dict=True,
			) or {}
		
		return {
			"billing_name": self.billing_name,
			"address": address.get("address_line1", ""),
			"city": address.get("city", ""),
			"state": address.get("state", ""),
			"country": address.get("country", ""),
			"postal_code": address.get("pincode", ""),
			"gstin": address.get("gstin", "")
		}

	def get_partner_level(self):
		# fetch partner level from framework.jingrow.com
		client = get_jingrow_io_connection()
		response = client.session.get(
			f"{client.url}/api/action/get_partner_level",
			headers=client.headers,
			params={"email": self.partner_email},
		)

		if response.ok:
			res = response.json()
			partner_level = res.get("message")
			certificate_count = res.get("certificates")
			if partner_level:
				return partner_level, certificate_count
			return None

		self.add_comment(text="Failed to fetch partner level" + "<br><br>" + response.text)
		return None

	def is_payment_mode_set(self):
		if self.payment_mode in ("Prepaid Credits", "Paid By Partner") or (
			self.payment_mode == "Card" and self.default_payment_method and self.billing_address
		):
			return True
		return False

	def get_onboarding(self):
		site_created = jingrow.db.count("Site", {"team": self.name}) > 0
		saas_site_request = self.get_pending_saas_site_request()
		is_payment_mode_set = self.is_payment_mode_set()
		if not is_payment_mode_set and self.parent_team:
			parent_team = jingrow.get_cached_pg("Team", self.parent_team)
			is_payment_mode_set = parent_team.is_payment_mode_set()

		complete = False
		if (
			is_payment_mode_set
			or jingrow.db.get_value("User", self.user, "user_type") == "System User"
			or has_role("Jcloud Support Agent")
		):
			complete = True
		elif saas_site_request:
			complete = False

		return jingrow._dict(
			{
				"site_created": site_created,
				"is_saas_user": bool(self.via_jerp or self.is_saas_user),
				"saas_site_request": saas_site_request,
				"complete": complete,
				"is_payment_mode_set": is_payment_mode_set,
			}
		)

	def get_route_on_login(self):
		if self.payment_mode:
			return "/sites"

		if self.is_saas_user:
			pending_site_request = self.get_pending_saas_site_request()
			if pending_site_request:
				return f"/create-site/{pending_site_request.product_trial}/setup?account_request={pending_site_request.account_request}"

		return "/welcome"

	def get_pending_saas_site_request(self):
		if jingrow.db.exists("Product Trial Request", {"team": self.name, "status": "Site Created"}):
			return None

		return jingrow.db.get_value(
			"Product Trial Request",
			{
				"team": self.name,
				"status": ("in", ["Pending", "Wait for Site", "Completing Setup Wizard", "Error"]),
			},
			["name", "product_trial", "product_trial.title", "status", "account_request"],
			order_by="creation desc",
			as_dict=True,
		)

	def get_trial_sites(self):
		return jingrow.db.get_all(
			"Site",
			{
				"team": self.name,
				"is_standby": False,
				"trial_end_date": ("is", "set"),
				"status": ("!=", "Archived"),
			},
			["name", "trial_end_date", "standby_for_product.title as product_title", "host_name"],
			order_by="`tabSite`.`modified` desc",
		)

	@jingrow.whitelist()
	def suspend_sites(self, reason=None):
		from jcloud.jcloud.pagetype.site.site import Site

		sites_to_suspend = self.get_sites_to_suspend()
		for site in sites_to_suspend:
			try:
				Site("Site", site).suspend(reason, skip_reload=True)
			except Exception:
				log_error("Failed to Suspend Sites", traceback=jingrow.get_traceback())
		return sites_to_suspend

	def get_sites_to_suspend(self):
		plan = jingrow.qb.PageType("Site Plan")
		query = (
			jingrow.qb.from_(plan)
			.select(plan.name)
			.where((plan.enabled == 1) & ((plan.is_jingrow_plan == 1) | (plan.is_trial_plan == 1)))
		).run(as_dict=True)
		jingrow_plans = [d.name for d in query]

		return jingrow.db.get_all(
			"Site",
			{
				"team": self.name,
				"status": ("not in", ("Archived", "Suspended")),
				"free": 0,
				"plan": ("not in", jingrow_plans),
			},
			pluck="name",
		)

	def reallocate_workers_if_needed(
		self, workloads_before: list[str, float, str], workloads_after: list[str, float, str]
	):
		for before, after in zip(workloads_before, workloads_after):
			if after[1] - before[1] >= 8:  # 100 USD equivalent
				jingrow.enqueue_pg(
					"Server",
					before[2],
					method="auto_scale_workers",
					job_id=f"auto_scale_workers:{before[2]}",
					deduplicate=True,
					enqueue_after_commit=True,
				)

	@jingrow.whitelist()
	def unsuspend_sites(self, reason=None):
		from jcloud.jcloud.pagetype.bench.bench import Bench
		from jcloud.jcloud.pagetype.site.site import Site

		suspended_sites = [
			d.name for d in jingrow.db.get_all("Site", {"team": self.name, "status": "Suspended"})
		]
		workloads_before = list(Bench.get_workloads(suspended_sites))
		for site in suspended_sites:
			Site("Site", site).unsuspend(reason, skip_reload=True)
		workloads_after = list(Bench.get_workloads(suspended_sites))
		self.reallocate_workers_if_needed(workloads_before, workloads_after)

		return suspended_sites

	def remove_subscription_config_in_trial_sites(self):
		for site in jingrow.db.get_all(
			"Site",
			{"team": self.name, "status": ("!=", "Archived"), "trial_end_date": ("is", "set")},
			pluck="name",
		):
			try:
				jingrow.get_pg("Site", site).update_site_config(
					{
						"subscription": {"status": "Subscribed"},
					}
				)
			except Exception:
				log_error("Failed to remove subscription config in trial sites")

	def get_upcoming_invoice(self, for_update=False):
		# get the current period's invoice
		today = jingrow.utils.today()
		result = jingrow.db.get_all(
			"Invoice",
			filters={
				"status": "Draft",
				"team": self.name,
				"type": "Subscription",
				"period_start": ("<=", today),
				"period_end": (">=", today),
			},
			order_by="creation desc",
			limit=1,
			pluck="name",
		)
		if result:
			return jingrow.get_pg("Invoice", result[0], for_update=for_update)
		return None

	def create_upcoming_invoice(self):
		today = jingrow.utils.today()
		
		# 获取团队关联用户的email
		user_email = jingrow.db.get_value("User", self.user, "email")
		
		# 如果用户没有email，则使用备选邮箱格式
		customer_email = user_email or f"{self.name}@example.com"
		
		# 创建发票时使用用户email
		invoice = jingrow.get_pg({
			"pagetype": "Invoice",
			"team": self.name,
			"date": today,
			"status": "Draft",
			"customer_email": customer_email,
			# 其他字段...
		}).insert()
		
		return invoice

	def notify_with_email(self, recipients: list[str], **kwargs):
		if not self.send_notifications:
			return
		if not recipients:
			recipients = [self.notify_email]

		jingrow.sendmail(recipients=recipients, **kwargs)

	@jingrow.whitelist()
	def send_telegram_alert_for_failed_payment(self, invoice):
		team_url = get_url_to_form("Team", self.name)
		invoice_url = get_url_to_form("Invoice", invoice)
		message = f"Failed Invoice Payment [{invoice}]({invoice_url}) of Partner: [{self.name}]({team_url})"
		TelegramMessage.enqueue(message=message)

	@jingrow.whitelist()
	def send_email_for_failed_payment(self, invoice, sites=None):
		invoice = jingrow.get_pg("Invoice", invoice)
		email = (
			jingrow.db.get_value("Communication Email", {"parent": self.name, "type": "invoices"}, "value")
			or self.user
		)
		payment_method = self.default_payment_method
		last_4 = jingrow.db.get_value("Stripe Payment Method", payment_method, "last_4")
		account_update_link = jingrow.utils.get_url("/dashboard")
		subject = "Invoice Payment Failed for Jingrow Subscription"

		jingrow.sendmail(
			recipients=email,
			subject=subject,
			template="payment_failed_partner" if self.jerp_partner else "payment_failed",
			args={
				"subject": subject,
				"payment_link": invoice.stripe_invoice_url,
				"amount": invoice.get_formatted("amount_due"),
				"account_update_link": account_update_link,
				"last_4": last_4 or "",
				"card_not_added": not payment_method,
				"sites": sites,
				"team": self,
			},
		)


def get_team_members(team):
	if not jingrow.db.exists("Team", team):
		return []

	r = jingrow.db.get_all("Team Member", filters={"parent": team}, fields=["user"])
	member_emails = [d.user for d in r]

	users = []
	if member_emails:
		users = jingrow.db.sql(
			"""
				select
					u.name,
					u.first_name,
					u.last_name,
					u.full_name,
					u.user_image,
					u.name as email,
					GROUP_CONCAT(r.`role`) as roles
				from `tabUser` u
				left join `tabHas Role` r
				on (r.parent = u.name)
				where u.name in %s
				group by u.name
			""",
			[member_emails],
			as_dict=True,
		)
		for user in users:
			user.roles = (user.roles or "").split(",")

	return users


def get_child_team_members(team):
	if not jingrow.db.exists("Team", team):
		return []

	# a child team cannot be parent to another child team
	if jingrow.get_value("Team", team, "parent_team"):
		return []

	child_team_members = [d.name for d in jingrow.db.get_all("Team", {"parent_team": team}, ["name"])]

	child_teams = []
	if child_team_members:
		child_teams = jingrow.db.sql(
			"""
				select t.name, t.team_title, t.parent_team, t.user
				from `tabTeam` t
				where t.name in %s
				and t.enabled = 1
			""",
			[child_team_members],
			as_dict=True,
		)

	return child_teams


def get_default_team(user):
	if jingrow.db.exists("Team", user):
		return user
	return None


def process_stripe_webhook(pg, method):
	"""This method runs after a Stripe Webhook Log is created"""

	if pg.event_type not in ["payment_intent.succeeded"]:
		return

	event = jingrow.parse_json(pg.payload)
	payment_intent = event["data"]["object"]
	if payment_intent.get("invoice"):
		# ignore payment for invoice
		return

	metadata = payment_intent.get("metadata")
	payment_for = metadata.get("payment_for")

	if payment_for and payment_for == "micro_debit_test_charge":
		process_micro_debit_test_charge(event)
		return

	if payment_for and payment_for == "partnership_fee":
		process_partnership_fee(payment_intent)
		return

	handle_payment_intent_succeeded(payment_intent)


def handle_payment_intent_succeeded(payment_intent):  # noqa: C901
	from datetime import datetime

	if isinstance(payment_intent, str):
		stripe = get_stripe()
		payment_intent = stripe.PaymentIntent.retrieve(payment_intent)

	metadata = payment_intent.get("metadata")
	if jingrow.db.exists("Invoice", {"stripe_payment_intent_id": payment_intent["id"], "status": "Paid"}):
		# ignore creating if already allocated
		return

	if not jingrow.db.exists("Team", {"stripe_customer_id": payment_intent["customer"]}):
		# might be checkout session payment
		# log the stripe webhook log
		# TODO: handle checkout session payment
		return
	team: Team = jingrow.get_pg("Team", {"stripe_customer_id": payment_intent["customer"]})
	amount_with_tax = payment_intent["amount"] / 100
	gst = float(metadata.get("gst", 0))
	amount = amount_with_tax - gst
	balance_transaction = team.allocate_credit_amount(
		amount, source="Prepaid Credits", remark=payment_intent["id"]
	)

	team.remove_subscription_config_in_trial_sites()
	invoice = jingrow.get_pg(
		pagetype="Invoice",
		team=team.name,
		type="Prepaid Credits",
		status="Paid",
		due_date=datetime.fromtimestamp(payment_intent["created"]),
		total=amount,
		amount_due=amount,
		gst=gst or 0,
		amount_due_with_tax=amount_with_tax,
		amount_paid=amount_with_tax,
		stripe_payment_intent_id=payment_intent["id"],
	)
	invoice.append(
		"items",
		{
			"description": "Prepaid Credits",
			"document_type": "Balance Transaction",
			"document_name": balance_transaction.name,
			"quantity": 1,
			"rate": amount,
		},
	)
	invoice.insert()
	invoice.reload()

	if not team.payment_mode:
		jingrow.db.set_value("Team", team.name, "payment_mode", "Prepaid Credits")
		if team.account_request:
			ar: "AccountRequest" = jingrow.get_pg("Account Request", team.account_request)
			if not (ar.is_saas_signup() or ar.invited_by_parent_team):
				capture("added_card_or_prepaid_credits", "fc_signup", team.user)

	# latest stripe API sets charge id in latest_charge
	charge = payment_intent.get("latest_charge")
	if not charge:
		# older stripe API sets charge id in charges.data
		charges = payment_intent.get("charges", {}).get("data", [])
		charge = charges[0]["id"] if charges else None
	if charge:
		# update transaction amount, fee and exchange rate
		invoice.update_transaction_details(charge)
		invoice.submit()

	_enqueue_finalize_unpaid_invoices_for_team(team.name)


def _enqueue_finalize_unpaid_invoices_for_team(team: str):
	# Enqueue a background job to call finalize_draft_invoice for unpaid invoices
	jingrow.enqueue(
		"jcloud.jcloud.pagetype.team.team.enqueue_finalize_unpaid_for_team",
		team=team,
		queue="long",
	)


def enqueue_finalize_unpaid_for_team(team: str):
	# get a list of unpaid invoices for the team
	invoices = jingrow.get_all(
		"Invoice",
		filters={"team": team, "status": "Unpaid", "type": "Subscription"},
		pluck="name",
	)

	# Enqueue a background job to call finalize_invoice
	for invoice in invoices:
		pg = jingrow.get_pg("Invoice", invoice)
		pg.finalize_invoice()


def process_partnership_fee(payment_intent):
	from datetime import datetime

	if isinstance(payment_intent, str):
		stripe = get_stripe()
		payment_intent = stripe.PaymentIntent.retrieve(payment_intent)

	metadata = payment_intent.get("metadata")
	if jingrow.db.exists("Invoice", {"stripe_payment_intent_id": payment_intent["id"], "status": "Paid"}):
		# ignore creating duplicate partnership fee invoice
		return

	team = jingrow.get_pg("Team", {"stripe_customer_id": payment_intent["customer"]})
	amount_with_tax = payment_intent["amount"] / 100
	gst = float(metadata.get("gst", 0))
	amount = amount_with_tax - gst
	balance_transaction = team.allocate_credit_amount(
		amount, source="Prepaid Credits", remark=payment_intent["id"], type="Partnership Fee"
	)

	invoice = jingrow.get_pg(
		pagetype="Invoice",
		team=team.name,
		type="Partnership Fees",
		status="Paid",
		due_date=datetime.fromtimestamp(payment_intent["created"]),
		total=amount,
		amount_due=amount,
		gst=gst or 0,
		amount_due_with_tax=amount_with_tax,
		amount_paid=amount_with_tax,
		stripe_payment_intent_id=payment_intent["id"],
	)
	invoice.append(
		"items",
		{
			"description": "Partnership Fee",
			"document_type": "Balance Transaction",
			"document_name": balance_transaction.name,
			"quantity": 1,
			"rate": amount,
		},
	)
	invoice.insert()
	invoice.reload()

	# latest stripe API sets charge id in latest_charge
	charge = payment_intent.get("latest_charge")
	if not charge:
		# older stripe API sets charge id in charges.data
		charges = payment_intent.get("charges", {}).get("data", [])
		charge = charges[0]["id"] if charges else None
	if charge:
		# update transaction amount, fee and exchange rate
		invoice.update_transaction_details(charge)
		invoice.submit()


def get_permission_query_conditions(user):
	from jcloud.utils import get_current_team

	if not user:
		user = jingrow.session.user

	user_type = jingrow.db.get_value("User", user, "user_type", cache=True)
	if user_type == "System User":
		return ""

	team = get_current_team()

	return f"(`tabTeam`.`name` = {jingrow.db.escape(team)})"


def has_permission(pg, ptype, user):
	from jcloud.utils import get_current_team

	if not user:
		user = jingrow.session.user

	user_type = jingrow.db.get_value("User", user, "user_type", cache=True)
	if user_type == "System User":
		return True

	team = get_current_team(True)
	child_team_members = [d.name for d in jingrow.db.get_all("Team", {"parent_team": team.name}, ["name"])]
	if pg.name == team.name or pg.name in child_team_members:
		return True

	return False


def validate_site_creation(pg, method):
	if jingrow.session.user == "Administrator":
		return
	if not pg.team:
		return
	# allow product signups
	if pg.standby_for_product:
		return

	# validate site creation for team
	team = jingrow.get_pg("Team", pg.team)
	[allow_creation, why] = team.can_create_site()
	if not allow_creation:
		jingrow.throw(why)


def has_unsettled_invoices(team):
	if not jingrow.db.exists(
		"Invoice", {"team": team, "status": ("in", ("Unpaid", "Draft")), "type": "Subscription"}
	):
		return False

	currency = jingrow.db.get_value("Team", team, "currency")
	minimum_amount = 5
	if currency == "CNY":
		minimum_amount = 450

	data = jingrow.get_all(
		"Invoice",
		{"team": team, "status": ("in", ("Unpaid", "Draft")), "type": "Subscription"},
		["sum(amount_due) as amount_due"],
	)[0]
	if data.amount_due <= minimum_amount:
		return False
	return True


def is_us_eu():
	"""Is the customer from U.S. or European Union"""
	from jcloud.utils import get_current_team

	countrygroup = [
		"United States",
		"United Kingdom",
		"Austria",
		"Belgium",
		"Bulgaria",
		"Croatia",
		"Republic of Cyprus",
		"Czech Republic",
		"Denmark",
		"Estonia",
		"Finland",
		"France",
		"Germany",
		"Greece",
		"Hungary",
		"Ireland",
		"Italy",
		"Latvia",
		"Lithuania",
		"Luxembourg",
		"Malta",
		"Netherlands",
		"Poland",
		"Portugal",
		"Romania",
		"Slovakia",
		"Slovenia",
		"Spain",
		"Sweden",
		"Switzerland",
		"Australia",
		"New Zealand",
		"Canada",
		"Mexico",
	]
	return jingrow.db.get_value("Team", get_current_team(), "country") in countrygroup
