# Copyright (c) 2020, JINGROW
# For license information, please see license.txt

from __future__ import annotations

import jingrow
import rq
from jingrow.model.page import Page
from jingrow.query_builder.functions import Coalesce, Count
from jingrow.utils import cint, flt

from jcloud.overrides import get_permission_query_conditions_for_pagetype
from jcloud.jcloud.pagetype.site_plan.site_plan import SitePlan
from jcloud.utils import log_error
from jcloud.utils.jobs import has_job_timeout_exceeded


class Subscription(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

		additional_storage: DF.Data | None
		document_name: DF.DynamicLink
		document_type: DF.Link
		enabled: DF.Check
		interval: DF.Literal["Daily", "Monthly"]
		marketplace_app_subscription: DF.Link | None
		plan: DF.DynamicLink
		plan_type: DF.Link
		secret_key: DF.Data | None
		site: DF.Link | None
		team: DF.Link
	# end: auto-generated types

	dashboard_fields = (
		"site",
		"enabled",
		"document_type",
		"document_name",
		"team",
	)

	@staticmethod
	def get_list_query(query, **list_args):
		Subscription = jingrow.qb.PageType("Subscription")
		UsageRecord = jingrow.qb.PageType("Usage Record")
		Plan = jingrow.qb.PageType("Marketplace App Plan")
		price_field = Plan.price_cny if jingrow.local.team().currency == "CNY" else Plan.price_usd
		filters = list_args.get("filters", {})

		query = (
			jingrow.qb.from_(Subscription)
			.join(Plan)
			.on(Subscription.plan == Plan.name)
			.left_join(UsageRecord)
			.on(UsageRecord.subscription == Subscription.name)
			.groupby(Subscription.name)
			.select(
				Subscription.site,
				Subscription.enabled,
				price_field.as_("price"),
				Coalesce(Count(UsageRecord.subscription), 0).as_("active_for"),
			)
			.where(
				(Subscription.document_type == "Marketplace App")
				& (Subscription.document_name == filters["document_name"])
				& (Subscription.site != "")
				& (price_field > 0)
			)
			.limit(list_args["limit"])
			.offset(list_args["start"])
		)

		if filters.get("enabled"):
			enabled = 1 if filters["enabled"] == "Active" else 0
			query = query.where(Subscription.enabled == enabled)

		return query.run(as_dict=True)

	def before_validate(self):
		if not self.secret_key and self.document_type == "Marketplace App":
			self.secret_key = jingrow.utils.generate_hash(length=40)
			if not jingrow.db.exists("Site Config Key", {"key": f"sk_{self.document_name}"}):
				jingrow.get_pg(
					pagetype="Site Config Key", internal=True, key=f"sk_{self.document_name}"
				).insert(ignore_permissions=True)

	def validate(self):
		self.validate_duplicate()

	def on_update(self):
		if self.plan_type == "Server Storage Plan":
			return

		pg = self.get_subscribed_document()
		plan_field = pg.meta.get_field("plan")
		if not (plan_field and plan_field.options in ["Site Plan", "Server Plan", "Marketplace App Plan"]):
			return

		if self.enabled and pg.plan != self.plan:
			pg.plan = self.plan
			pg.save()
		if not self.enabled and pg.plan:
			pg.plan = ""
			pg.save()

	def enable(self):
		if self.enabled:
			return
		try:
			self.enabled = True
			self.save(ignore_permissions=True)
		except Exception:
			jingrow.log_error(title="Enable Subscription Error")

	def disable(self):
		if not self.enabled:
			return
		try:
			self.enabled = False
			self.save(ignore_permissions=True)
		except Exception:
			jingrow.log_error(title="Disable Subscription Error")

	@jingrow.whitelist()
	def create_usage_record(self, date: DF.Date | None = None):
		cannot_charge = not self.can_charge_for_subscription()
		if cannot_charge:
			return None

		if self.is_usage_record_created(date):
			return None

		team = jingrow.get_cached_pg("Team", self.team)

		if team.parent_team:
			team = jingrow.get_cached_pg("Team", team.parent_team)

		if team.billing_team and team.payment_mode == "Paid By Partner":
			team = jingrow.get_cached_pg("Team", team.billing_team)

		if not team.get_upcoming_invoice():
			team.create_upcoming_invoice()

		plan = jingrow.get_cached_pg(self.plan_type, self.plan)

		if self.additional_storage:
			price = plan.price_cny if team.currency == "CNY" else plan.price_usd
			price_per_day = price / plan.period  # no rounding off to avoid discrepancies
			amount = flt((price_per_day * cint(self.additional_storage)), 2)
		else:
			amount = plan.get_price_for_interval(self.interval, team.currency)

		usage_record = jingrow.get_pg(
			pagetype="Usage Record",
			team=team.name,
			document_type=self.document_type,
			document_name=self.document_name,
			plan_type=self.plan_type,
			plan=plan.name,
			amount=amount,
			date=date,
			subscription=self.name,
			interval=self.interval,
			site=(
				self.site
				or jingrow.get_value("Marketplace App Subscription", self.marketplace_app_subscription, "site")
			)
			if self.document_type == "Marketplace App"
			else None,
		)
		usage_record.insert()
		usage_record.submit()
		return usage_record

	def can_charge_for_subscription(self):
		pg = self.get_subscribed_document()
		if not pg:
			return False

		if hasattr(pg, "can_charge_for_subscription"):
			return pg.can_charge_for_subscription(self)

		return True

	def is_usage_record_created(self, date=None):
		filters = {
			"team": self.team,
			"document_type": self.document_type,
			"document_name": self.document_name,
			"subscription": self.name,
			"interval": self.interval,
			"plan": self.plan,
		}

		if self.interval == "Daily":
			date = date or jingrow.utils.today()
			filters.update({"date": date})

		if self.interval == "Monthly":
			date = jingrow.utils.getdate()
			first_day = jingrow.utils.get_first_day(date)
			last_day = jingrow.utils.get_last_day(date)
			filters.update({"date": ("between", (first_day, last_day))})

		result = jingrow.db.get_all("Usage Record", filters=filters, limit=1)
		return bool(result)

	def validate_duplicate(self):
		if not self.is_new():
			return
		filters = {
			"team": self.team,
			"document_type": self.document_type,
			"document_name": self.document_name,
			"plan_type": self.plan_type,
		}
		if self.document_type == "Marketplace App":
			filters.update({"marketplace_app_subscription": self.marketplace_app_subscription})

		results = jingrow.db.get_all(
			"Subscription",
			filters,
			pluck="name",
			limit=1,
			ignore_ifnull=True,
		)
		if results:
			link = jingrow.utils.get_link_to_form("Subscription", results[0])
			jingrow.throw(f"A Subscription already exists: {link}", jingrow.DuplicateEntryError)

	def get_subscribed_document(self):
		if not hasattr(self, "_subscribed_document"):
			self._subscribed_document = jingrow.get_pg(self.document_type, self.document_name)
		return self._subscribed_document

	@classmethod
	def get_sites_without_offsite_backups(cls) -> list[str]:
		plans = SitePlan.get_ones_without_offsite_backups()
		return jingrow.get_all(
			"Subscription",
			filters={"document_type": "Site", "plan": ("in", plans)},
			pluck="document_name",
		)


def create_usage_records():
	"""
	Creates daily usage records for paid Subscriptions
	"""
	free_sites = sites_with_free_hosting()
	settings = jingrow.get_single("Jcloud Settings")
	subscriptions = jingrow.db.get_all(
		"Subscription",
		filters={
			"enabled": True,
			"plan": ("in", paid_plans()),
			"name": ("not in", created_usage_records(free_sites)),
			"document_name": ("not in", free_sites),
		},
		pluck="name",
		order_by=None,
		limit=settings.usage_record_creation_batch_size or 500,
		ignore_ifnull=True,
		debug=True,
	)
	for name in subscriptions:
		if has_job_timeout_exceeded():
			return
		subscription = jingrow.get_cached_pg("Subscription", name)
		try:
			subscription.create_usage_record()
			jingrow.db.commit()
		except rq.timeouts.JobTimeoutException:
			# This job took too long to execute
			# We need to rollback the transaction
			# Try again in the next job
			jingrow.db.rollback()
			return
		except Exception:
			jingrow.db.rollback()
			log_error(title="Create Usage Record Error", name=name)


def paid_plans():
	paid_plans = []
	filter = {
		"price_cny": (">", 0),
		"enabled": 1,
	}
	pagetypes = [
		"Site Plan",
		"Marketplace App Plan",
		"Server Plan",
		"Server Storage Plan",
		"Cluster Plan",
	]
	for pagetype in pagetypes:
		paid_plans += jingrow.get_all(pagetype, filter, pluck="name", ignore_ifnull=True)

	return list(set(paid_plans))


def sites_with_free_hosting():
	# sites marked as free
	free_teams = jingrow.get_all("Team", filters={"free_account": True, "enabled": True}, pluck="name")
	free_team_sites = jingrow.get_all(
		"Site",
		{"status": ("not in", ("Archived", "Suspended")), "team": ("in", free_teams)},
		pluck="name",
		ignore_ifnull=True,
	)
	return free_team_sites + jingrow.get_all(
		"Site",
		filters={
			"free": True,
			"status": ("not in", ("Archived", "Suspended")),
			"team": ("not in", free_teams),
		},
		pluck="name",
		ignore_ifnull=True,
	)


def created_usage_records(free_sites, date=None):
	date = date or jingrow.utils.today()
	"""Returns created usage records for a particular date"""
	return jingrow.get_all(
		"Usage Record",
		filters={
			"document_type": (
				"in",
				(
					"Site",
					"Server",
					"Database Server",
					"Self Hosted Server",
					"Marketplace App",
					"Cluster",
				),
			),
			"date": date,
			"document_name": ("not in", free_sites),
		},
		pluck="subscription",
		order_by=None,
		ignore_ifnull=True,
	)


get_permission_query_conditions = get_permission_query_conditions_for_pagetype("Subscription")
