"""services phase 4A due date engine foundation

Revision ID: 20260504_due_date_engine_foundation
Revises: 20260503_assurance_review_partner
Create Date: 2026-05-04
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


revision: str = "20260504_due_date_engine_foundation"
down_revision: Union[str, None] = "20260503_assurance_review_partner"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def _insp():
    return sa.inspect(op.get_bind())


def _has_table(table_name: str) -> bool:
    return table_name in _insp().get_table_names()


def _has_column(table_name: str, column_name: str) -> bool:
    if not _has_table(table_name):
        return False
    return any(c["name"] == column_name for c in _insp().get_columns(table_name))


def _has_index(table_name: str, index_name: str) -> bool:
    if not _has_table(table_name):
        return False
    return any(ix.get("name") == index_name for ix in _insp().get_indexes(table_name))


def _create_index_if_missing(table_name: str, index_name: str, columns: list[str]) -> None:
    if _has_table(table_name) and not _has_index(table_name, index_name):
        op.create_index(index_name, table_name, columns)


def upgrade() -> None:
    if not _has_table("service_due_date_rules"):
        op.create_table(
            "service_due_date_rules",
            sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
            sa.Column("service_catalogue_id", sa.Integer(), nullable=False),
            sa.Column("rule_name", sa.String(length=150), nullable=False),
            sa.Column("period_type", sa.String(length=30), nullable=False, server_default="yearly"),
            sa.Column("due_year_basis", sa.String(length=40), nullable=False, server_default="assessment_year_start"),
            sa.Column("due_day", sa.Integer(), nullable=True),
            sa.Column("due_month", sa.Integer(), nullable=True),
            sa.Column("due_month_offset", sa.Integer(), nullable=False, server_default="0"),
            sa.Column("days_offset_after_event", sa.Integer(), nullable=True),
            sa.Column("sort_order", sa.Integer(), nullable=False, server_default="100"),
            sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
            sa.Column("remarks", sa.Text(), nullable=True),
            sa.Column("created_by_user_id", sa.Integer(), nullable=True),
            sa.Column("updated_by_user_id", sa.Integer(), nullable=True),
            sa.Column("created_at_utc", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
            sa.Column("updated_at_utc", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
            sa.ForeignKeyConstraint(["service_catalogue_id"], ["service_catalogues.id"], ondelete="CASCADE"),
            sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"]),
            sa.ForeignKeyConstraint(["updated_by_user_id"], ["users.id"]),
            sa.UniqueConstraint("service_catalogue_id", "rule_name", name="uq_service_due_date_rules_catalogue_name"),
        )
    _create_index_if_missing("service_due_date_rules", "ix_service_due_date_rules_service_catalogue_id", ["service_catalogue_id"])
    _create_index_if_missing("service_due_date_rules", "ix_service_due_date_rules_period_type", ["period_type"])
    _create_index_if_missing("service_due_date_rules", "ix_service_due_date_rules_is_active", ["is_active"])

    if not _has_table("service_due_date_extensions"):
        op.create_table(
            "service_due_date_extensions",
            sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
            sa.Column("tenant_id", sa.Integer(), nullable=False),
            sa.Column("service_catalogue_id", sa.Integer(), nullable=False),
            sa.Column("due_date_rule_id", sa.Integer(), nullable=True),
            sa.Column("financial_year", sa.String(length=9), nullable=False),
            sa.Column("assessment_year", sa.String(length=9), nullable=True),
            sa.Column("period_label", sa.String(length=30), nullable=True),
            sa.Column("previous_due_date", sa.Date(), nullable=True),
            sa.Column("extended_due_date", sa.Date(), nullable=False),
            sa.Column("extension_sequence", sa.Integer(), nullable=False, server_default="1"),
            sa.Column("notification_reference", sa.String(length=200), nullable=True),
            sa.Column("notification_date", sa.Date(), nullable=True),
            sa.Column("remarks", sa.Text(), nullable=True),
            sa.Column("created_by_user_id", sa.Integer(), nullable=True),
            sa.Column("updated_by_user_id", sa.Integer(), nullable=True),
            sa.Column("created_at_utc", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
            sa.Column("updated_at_utc", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
            sa.ForeignKeyConstraint(["tenant_id"], ["tenants.id"], ondelete="CASCADE"),
            sa.ForeignKeyConstraint(["service_catalogue_id"], ["service_catalogues.id"], ondelete="CASCADE"),
            sa.ForeignKeyConstraint(["due_date_rule_id"], ["service_due_date_rules.id"], ondelete="SET NULL"),
            sa.ForeignKeyConstraint(["created_by_user_id"], ["users.id"]),
            sa.ForeignKeyConstraint(["updated_by_user_id"], ["users.id"]),
        )
    for index_name, columns in [
        ("ix_service_due_date_extensions_tenant_id", ["tenant_id"]),
        ("ix_service_due_date_extensions_service_catalogue_id", ["service_catalogue_id"]),
        ("ix_service_due_date_extensions_due_date_rule_id", ["due_date_rule_id"]),
        ("ix_service_due_date_extensions_financial_year", ["financial_year"]),
        ("ix_service_due_date_extensions_assessment_year", ["assessment_year"]),
        ("ix_service_due_date_extensions_period_label", ["period_label"]),
        ("ix_service_due_date_extensions_extended_due_date", ["extended_due_date"]),
    ]:
        _create_index_if_missing("service_due_date_extensions", index_name, columns)

    subscription_columns = [
        ("due_date_rule_id", sa.Column("due_date_rule_id", sa.Integer(), nullable=True)),
        ("original_due_date", sa.Column("original_due_date", sa.Date(), nullable=True)),
        ("current_due_date", sa.Column("current_due_date", sa.Date(), nullable=True)),
        ("due_date_source", sa.Column("due_date_source", sa.String(length=30), nullable=True)),
    ]
    for column_name, column in subscription_columns:
        if not _has_column("client_service_subscriptions", column_name):
            op.add_column("client_service_subscriptions", column)

    for index_name, columns in [
        ("ix_client_service_subscriptions_due_date_rule_id", ["due_date_rule_id"]),
        ("ix_client_service_subscriptions_original_due_date", ["original_due_date"]),
        ("ix_client_service_subscriptions_current_due_date", ["current_due_date"]),
    ]:
        _create_index_if_missing("client_service_subscriptions", index_name, columns)


def downgrade() -> None:
    for index_name in [
        "ix_client_service_subscriptions_current_due_date",
        "ix_client_service_subscriptions_original_due_date",
        "ix_client_service_subscriptions_due_date_rule_id",
    ]:
        if _has_index("client_service_subscriptions", index_name):
            op.drop_index(index_name, table_name="client_service_subscriptions")
    for column_name in ["due_date_source", "current_due_date", "original_due_date", "due_date_rule_id"]:
        if _has_column("client_service_subscriptions", column_name):
            op.drop_column("client_service_subscriptions", column_name)
    if _has_table("service_due_date_extensions"):
        op.drop_table("service_due_date_extensions")
    if _has_table("service_due_date_rules"):
        op.drop_table("service_due_date_rules")
