import logging
from pathlib import Path
from urllib.parse import quote
import environ
from datetime import timedelta
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.django import DjangoIntegration

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent

# Environment Variables
env = environ.Env()
environ.Env.read_env(env_file=BASE_DIR / '.env')

DEBUG = env.bool('DEBUG', default=False)

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY', default='your-secret-key')
BASE_URL = env('BASE_URL', default='http://127.0.0.1:8000')
BACKEND_URL = env('BACKEND_URL', default='http://127.0.0.1:8000')
FRONTEND_URL = env('FRONTEND_URL', default='http://127.0.0.1:3000')

SENTRY_DSN = env('SENTRY_DSN', default='')

# ── Cloudflare R2 (S3-compatible) ────────────────────────────────
# All media is stored in Cloudflare R2. ``R2_PUBLIC_BASE_URL`` should
# be either the bucket's R2.dev subdomain or a custom domain bound to
# the bucket (this is what ends up in DB-persisted media URLs).
R2_ACCOUNT_ID = env('R2_ACCOUNT_ID', default='').strip()
R2_ACCESS_KEY_ID = env('R2_ACCESS_KEY_ID', default='').strip()
R2_SECRET_ACCESS_KEY = env('R2_SECRET_ACCESS_KEY', default='').strip()
R2_BUCKET = env('R2_BUCKET', default='').strip()
R2_REGION = env('R2_REGION', default='auto').strip()
R2_ENDPOINT = env(
    'R2_ENDPOINT',
    default=(
        f'https://{R2_ACCOUNT_ID}.r2.cloudflarestorage.com'
        if R2_ACCOUNT_ID else ''
    ),
).strip()
R2_PUBLIC_BASE_URL = env('R2_PUBLIC_BASE_URL', default='').strip()
if R2_PUBLIC_BASE_URL and not R2_PUBLIC_BASE_URL.startswith(('http://', 'https://')):
    R2_PUBLIC_BASE_URL = f'https://{R2_PUBLIC_BASE_URL}'
R2_PUBLIC_BASE_URL = R2_PUBLIC_BASE_URL.rstrip('/')
R2_MEDIA_PATH = env('R2_MEDIA_PATH', default='').strip('/ ')

# Canonical public base for media URLs. Consumers (e.g. serializers
# validating that a URL lives on our own storage) should reference
# this rather than any backend-specific setting.
MEDIA_BASE_URL = R2_PUBLIC_BASE_URL

DATABASE_ENGINE = env('DATABASE_ENGINE', default='django.db.backends.sqlite3')
DATABASE_NAME = env('DATABASE_NAME', default='db.sqlite3')
DATABASE_USER = env('DATABASE_USER', default='your-database-user')
DATABASE_PASSWORD = env('DATABASE_PASSWORD', default='your-database-password')
DATABASE_HOST = env('DATABASE_HOST', default='localhost')
DATABASE_PORT = env('DATABASE_PORT', default='5432')

EMAIL_HOST = env('EMAIL_HOST', default='localhost')
EMAIL_HOST_USER = env('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD', default='')
EMAIL_PORT = env.int('EMAIL_PORT', default=587)
EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=True)
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='your-email@example.com')

# ── Azure Communication Services (Email) ──────────────────────────
# Production sends go through ACS when ``endpoint + access_key`` are
# configured. In dev / tests, leave them blank to fall back to
# ``console.EmailBackend``.
AZURE_COMMUNICATION_ENDPOINT = env(
    'AZURE_COMMUNICATION_ENDPOINT', default='',
)
AZURE_COMMUNICATION_ACCESS_KEY = env(
    'AZURE_COMMUNICATION_ACCESS_KEY', default='',
)
AZURE_COMMUNICATION_EMAIL_SENDER_NO_REPLY = env(
    'AZURE_COMMUNICATION_EMAIL_SENDER_NO_REPLY', default='',
)
AZURE_COMMUNICATION_EMAIL_SENDER_NOTIFICATION = env(
    'AZURE_COMMUNICATION_EMAIL_SENDER_NOTIFICATION', default='',
)
AZURE_COMMUNICATION_EMAIL_SENDER_SUPPORT = env(
    'AZURE_COMMUNICATION_EMAIL_SENDER_SUPPORT', default='',
)
AZURE_COMMUNICATION_EMAIL_SENDER_MARKETING = env(
    'AZURE_COMMUNICATION_EMAIL_SENDER_MARKETING', default='',
)
# Legacy single-sender env kept as a fallback. Most events should pick
# a purpose-specific sender via ``send_template_email(sender='...')``.
AZURE_COMMUNICATION_EMAIL_SENDER = env(
    'AZURE_COMMUNICATION_EMAIL_SENDER',
    default=(
        AZURE_COMMUNICATION_EMAIL_SENDER_NOTIFICATION
        or AZURE_COMMUNICATION_EMAIL_SENDER_NO_REPLY
        or AZURE_COMMUNICATION_EMAIL_SENDER_SUPPORT
        or AZURE_COMMUNICATION_EMAIL_SENDER_MARKETING
        or ''
    ),
)

# Canonical map used by ``core.services.azure.email`` to resolve a
# purpose keyword (``no_reply`` / ``notification`` / ``support`` /
# ``marketing``) to a concrete MailFrom address. Each slot falls back
# to the single-sender value so partial configs keep working.
AZURE_COMMUNICATION_EMAIL_SENDERS = {
    'no_reply': AZURE_COMMUNICATION_EMAIL_SENDER_NO_REPLY or AZURE_COMMUNICATION_EMAIL_SENDER,
    'notification': AZURE_COMMUNICATION_EMAIL_SENDER_NOTIFICATION or AZURE_COMMUNICATION_EMAIL_SENDER,
    'support': AZURE_COMMUNICATION_EMAIL_SENDER_SUPPORT or AZURE_COMMUNICATION_EMAIL_SENDER,
    'marketing': AZURE_COMMUNICATION_EMAIL_SENDER_MARKETING or AZURE_COMMUNICATION_EMAIL_SENDER,
}
AZURE_COMMUNICATION_EMAIL_DEFAULT_SENDER_KEY = env(
    'AZURE_COMMUNICATION_EMAIL_DEFAULT_SENDER_KEY', default='notification',
)

AZURE_COMMUNICATION_EMAIL_POLLING_INTERVAL_SECONDS = env.int(
    'AZURE_COMMUNICATION_EMAIL_POLLING_INTERVAL_SECONDS', default=2,
)
AZURE_COMMUNICATION_EMAIL_TIMEOUT_SECONDS = env.int(
    'AZURE_COMMUNICATION_EMAIL_TIMEOUT_SECONDS', default=60,
)

_ACS_HAS_CREDENTIALS = bool(
    AZURE_COMMUNICATION_ENDPOINT and AZURE_COMMUNICATION_ACCESS_KEY
)

if _ACS_HAS_CREDENTIALS and AZURE_COMMUNICATION_EMAIL_SENDER:
    EMAIL_BACKEND = (
        'core.services.azure.email.backend.AzureCommunicationEmailBackend'
    )
    # Honour the ACS sender as the default From if none was set explicitly.
    if DEFAULT_FROM_EMAIL == 'your-email@example.com':
        DEFAULT_FROM_EMAIL = AZURE_COMMUNICATION_EMAIL_SENDER
else:
    EMAIL_BACKEND = env(
        'EMAIL_BACKEND',
        default='django.core.mail.backends.console.EmailBackend',
    )

JWT_ACCESS_TTL_MINUTES = env.int('JWT_ACCESS_TTL_MINUTES', default=15)

REDIS_HOST = env('REDIS_HOST', default='localhost')
REDIS_USER = env('REDIS_USER', default='')
REDIS_PASSWORD = env('REDIS_PASSWORD', default='')
REDIS_PORT = env('REDIS_PORT', default='6379')
# User and password are URL-encoded: generated passwords often contain "/", "+", "@" or "=",
# which would otherwise break the address. REDIS_URL, if set, wins.
_REDIS_AUTH = (f"{quote(REDIS_USER, safe='')}:{quote(REDIS_PASSWORD, safe='')}@" if REDIS_PASSWORD else '')
REDIS_URL = env('REDIS_URL', default=f"redis://{_REDIS_AUTH}{REDIS_HOST}:{REDIS_PORT}/0")

BEEM_API_KEY = env('BEEM_API_KEY', default='your-beem-api-key')
SELCOM_MERCHANT_ID = env('SELCOM_MERCHANT_ID', default='your-selcom-merchant-id')
CLICKPESA_CLIENT_ID = env('CLICKPESA_CLIENT_ID', default='your-clickpesa-client-id')
POLAR_API_KEY = env('POLAR_API_KEY', default='your-polar-api-key')

# Hosts and origins come from the environment, never a wildcard. Set the real domains in production.
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['localhost', '127.0.0.1'])

CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = env.list(
    'CORS_ALLOWED_ORIGINS',
    default=[
        'http://127.0.0.1:3000',
        'http://localhost:3000',
        'http://127.0.0.1:5173',
        'http://localhost:5173',
    ],
)
# Django checks the Origin of every HTTPS form POST (the admin login is one). The API's own origin is
# trusted by default; list the others - e.g. a frontend that posts forms here - in the environment.
CSRF_TRUSTED_ORIGINS = env.list('CSRF_TRUSTED_ORIGINS', default=[BACKEND_URL])


SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=JWT_ACCESS_TTL_MINUTES),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=14),
    # A refresh hands out a new refresh token and blacklists the old one, so a stolen refresh
    # token stops working the first time either party uses it.
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
    'UPDATE_LAST_LOGIN': True,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,
    'JWK_URL': None,
    'LEEWAY': 0,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
    'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),

    'AUTH_COOKIE': 'access_token',  # Cookie name. Enables cookies if value is set.
    'AUTH_COOKIE_DOMAIN': None,     # A string like "example.com", or None for standard domain cookie.
    'AUTH_COOKIE_SECURE': not DEBUG,  # Whether the auth cookies should be secure (https:// only).
    'AUTH_COOKIE_HTTP_ONLY' : True, # Http only cookie flag.It's not fetch by javascript.
    'AUTH_COOKIE_PATH': '/',        # The path of the auth cookie.
    'AUTH_COOKIE_SAMESITE': 'Lax',  # Whether to set the flag restricting cookie leaks on cross-site requests. This can be 'Lax', 'Strict', or None to disable the flag.
}

INSTALLED_APPS = [{auth_installed_app}
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    'rest_framework',
    'rest_framework.authtoken',
    'rest_framework_simplejwt',
    'rest_framework_simplejwt.token_blacklist',
    'django_celery_beat',
    'corsheaders',
    'drf_spectacular',
    # Project apps
    'apps.common',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # As high as possible and before CommonMiddleware, or responses CommonMiddleware ends early
    # (redirects, 404s) go out without CORS headers and the browser blocks them.
    'corsheaders.middleware.CorsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = '{project_name}.urls'
WSGI_APPLICATION = '{project_name}.wsgi.application'
ASGI_APPLICATION = '{project_name}.asgi.application'

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

DATABASES = {
    'default': {
        'ENGINE': DATABASE_ENGINE,
        'NAME': DATABASE_NAME,
        'USER': DATABASE_USER,
        'PASSWORD': DATABASE_PASSWORD,
        'HOST': DATABASE_HOST,
        'PORT': DATABASE_PORT,
        # Keep each worker thread's connection between requests instead of opening a new one per
        # request; health checks drop a connection the server has closed before it's reused.
        'CONN_MAX_AGE': env.int('DATABASE_CONN_MAX_AGE', default=60),
        'CONN_HEALTH_CHECKS': True,
    }
}

AUTH_PASSWORD_VALIDATORS = [
    {
        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
    },
    {
        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
    },
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    # Every successful response is {"status": "success", "message": ..., "data": ...} - including
    # plain Response(...) and paginated lists - so clients handle one shape (core/renderers.py).
    'DEFAULT_RENDERER_CLASSES': [
        'core.renderers.EnvelopeJSONRenderer',
    ],
    'EXCEPTION_HANDLER': 'core.exceptions.handler.exception_handler',
    'DEFAULT_PAGINATION_CLASS': 'core.pagination.StandardResultsSetPagination',
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    # How many of our own proxies append to X-Forwarded-For before Django. Throttles and
    # core.utils.request.client_ip take the visitor's address that many entries from the end;
    # the start of the header is whatever the visitor sent. None (development) trusts the header;
    # production sets it (settings/prod.py).
    'NUM_PROXIES': env.int('NUM_PROXIES', default=None),
    'DEFAULT_THROTTLE_RATES': {
        'anon': env('THROTTLE_ANON', default='120/minute'),
        'user': env('THROTTLE_USER', default='600/minute'),
        # Sign-in, registration and password resets: against password guessing.
        'auth': env('THROTTLE_AUTH', default='10/minute'),
        # Token refresh runs on every page load, and staff in one office share an IP.
        'auth_refresh': env('THROTTLE_AUTH_REFRESH', default='120/minute'),
    },
}

SPECTACULAR_SETTINGS = {
    'TITLE': '{original_name} API',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
    'COMPONENT_SPLIT_REQUEST': True,
    # The schema shows the envelope the renderer really sends (core/schema.py).
    'POSTPROCESSING_HOOKS': [
        'drf_spectacular.hooks.postprocess_schema_enums',
        'core.schema.wrap_responses_in_envelope',
    ],
}

# /api/v1/docs/ and /api/v1/schema/ disclose the whole API. Public in development; in production
# only staff signed in to the Django admin can open them, unless API_DOCS_PUBLIC=True.
API_DOCS_PUBLIC = env.bool('API_DOCS_PUBLIC', default=DEBUG)
{auth_user_model}
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

# Uploaded files (FileField/ImageField, e.g. User.profile_picture). With R2 configured they go to the
# bucket and are served from R2_PUBLIC_BASE_URL; without it (development) they're kept in MEDIA_ROOT.
# A container's disk is wiped on every deploy and nginx doesn't serve /media/, so production needs R2 -
# `manage.py check` warns when it isn't set up (apps/common/checks.py).
MEDIA_IN_R2 = bool(R2_ENDPOINT and R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY and R2_BUCKET and R2_PUBLIC_BASE_URL)
STORAGES = {
    'default': {
        'BACKEND': 'storages.backends.s3.S3Storage',
        'OPTIONS': {
            'endpoint_url': R2_ENDPOINT,
            'access_key': R2_ACCESS_KEY_ID,
            'secret_key': R2_SECRET_ACCESS_KEY,
            'bucket_name': R2_BUCKET,
            'region_name': R2_REGION,
            'location': R2_MEDIA_PATH,
            'custom_domain': R2_PUBLIC_BASE_URL.split('://', 1)[-1],
            'file_overwrite': False,
            'querystring_auth': False,  # public URLs, served from the custom domain
        },
    } if MEDIA_IN_R2 else {'BACKEND': 'django.core.files.storage.FileSystemStorage'},
    'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'},
}

# HTTPS-only settings live in settings/prod.py. Here, they read DEBUG from .env before dev.py set
# it, so a fresh project redirected every local request to https.

# ── Cache ───────────────────────────────────────────────────────────
# In-process in development; production uses Redis (settings/prod.py) so rate limits are counted
# across every worker.
CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}

# ── Celery ──────────────────────────────────────────────────────────
# {project_name}/__init__.py loads the Celery app, so .delay() uses this broker rather than
# Celery's default (AMQP on localhost).
CELERY_BROKER_URL = REDIS_URL
CELERY_RESULT_BACKEND = REDIS_URL
CELERY_BROKER_CONNECTION_TIMEOUT = 5
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'UTC'
CELERY_TASK_DEFAULT_QUEUE = 'default'
# The schedule lives in the database (django_celery_beat): CELERY_BEAT_SCHEDULE below is loaded into it
# when beat starts, and jobs can be added or paused in the Django admin (Periodic tasks).
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
# Periodic jobs, run by celery beat in the tasks container (.entry/start_tasks_backend.sh), e.g.
#   'nightly-cleanup': {'task': 'apps.common.tasks.cleanup', 'schedule': crontab(hour=1, minute=0)},
CELERY_BEAT_SCHEDULE = {}

# ── Logging ─────────────────────────────────────────────────────────
# Everything to stdout/stderr, where the container platform collects it.
LOG_LEVEL = env('LOG_LEVEL', default='INFO')
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {'format': '[{asctime}] {levelname} {name}: {message}', 'style': '{'},
    },
    'handlers': {
        'console': {'class': 'logging.StreamHandler', 'formatter': 'verbose'},
    },
    'root': {'handlers': ['console'], 'level': LOG_LEVEL},
    'loggers': {
        'django': {'handlers': ['console'], 'level': 'INFO', 'propagate': False},
        # The Azure SDK logs every HTTP request at INFO; keep only its warnings and errors.
        'azure': {'handlers': ['console'], 'level': 'WARNING', 'propagate': False},
    },
}

# ── Sentry Configuration ───────────────────────────────────────────
sentry_logging = LoggingIntegration(
    level=logging.INFO,        # INFO and above are kept as breadcrumbs on each event
    event_level=logging.ERROR,  # ERROR and above are sent as events
)

if SENTRY_DSN:
    sentry_sdk.init(
        dsn=SENTRY_DSN,
        # Add data like request headers and IP for users,
        # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
        # Users' IPs, cookies and headers go to Sentry only if the project opts in.
        send_default_pii=env.bool('SENTRY_SEND_PII', default=False),
        enable_logs=True,
        integrations=[
            sentry_logging,
            DjangoIntegration(),
        ],
        traces_sample_rate=env.float('SENTRY_TRACES_SAMPLE_RATE', default=0.1),
        profiles_sample_rate=env.float('SENTRY_PROFILES_SAMPLE_RATE', default=0.1),
    )
