from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login, logout, update_session_auth_hash
from django.contrib.auth.decorators import login_required
from django.core.cache import cache
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.decorators.http import require_http_methods

from home.stats import get_platform_stats

from accounts.decorators import seeker_required
from companies.follow_notifications import (
    company_profile_changed,
    company_profile_snapshot,
    notify_followers_profile_updated,
)

from .forms import (
    AccountPasswordChangeForm,
    AccountSettingsForm,
    AdminSetPasswordForm,
    AdminUserForm,
    CompanyRegistrationForm,
    LoginForm,
    SeekerRegistrationForm,
)
from .models import User, UserNotification


def _redirect_for_user(user, next_url=None, request=None):
    if next_url and request and url_has_allowed_host_and_scheme(
        next_url,
        allowed_hosts={request.get_host(), *settings.ALLOWED_HOSTS},
    ):
        return redirect(next_url)
    if user.is_platform_admin:
        return redirect('platform_admin:dashboard')
    if user.is_company:
        return redirect('employers:dashboard')
    return redirect('seekers:dashboard')


LOGIN_MAX_FAILURES = 5
LOGIN_LOCKOUT_SECONDS = 15 * 60


def _client_ip(request):
    forwarded = request.META.get('HTTP_X_FORWARDED_FOR', '')
    return (forwarded.split(',')[0].strip() if forwarded else request.META.get('REMOTE_ADDR', '')) or 'unknown'


def _login_throttle_key(request, email):
    return f"login-fail:{_client_ip(request)}:{(email or '').strip().lower()}"


@require_http_methods(['GET', 'POST'])
def login_view(request):
    if request.user.is_authenticated:
        return _redirect_for_user(request.user)

    next_url = request.GET.get('next', '')
    form = LoginForm(request.POST or None)
    locked = False
    if request.method == 'POST':
        key = _login_throttle_key(request, request.POST.get('email'))
        if cache.get(key, 0) >= LOGIN_MAX_FAILURES:
            locked = True
        elif form.is_valid():
            cache.delete(key)
            login(request, form.user)
            if request.POST.get('remember'):
                request.session.set_expiry(settings.SESSION_COOKIE_AGE)
            else:
                request.session.set_expiry(0)
            messages.success(request, f'Welcome back, {form.user.get_short_name() or form.user.email}!')
            post_next = request.POST.get('next') or next_url
            return _redirect_for_user(form.user, post_next, request)
        else:
            cache.set(key, cache.get(key, 0) + 1, LOGIN_LOCKOUT_SECONDS)

    response = render(request, 'accounts/login.html', {
        'form': form,
        'next': next_url or request.POST.get('next', ''),
        'stats': get_platform_stats(),
        'locked': locked,
    })
    if locked:
        response.status_code = 429
    return response


@require_http_methods(['POST', 'GET'])
def logout_view(request):
    logout(request)
    messages.info(request, 'You have been signed out.')
    return redirect('home:index')


@require_http_methods(['GET', 'POST'])
def register_view(request):
    if request.user.is_authenticated:
        return _redirect_for_user(request.user)

    account_type = request.POST.get('account_type', request.GET.get('type', 'seeker'))
    if account_type not in ('seeker', 'company'):
        account_type = 'seeker'

    if account_type == 'company':
        form = CompanyRegistrationForm(request.POST or None)
    else:
        form = SeekerRegistrationForm(request.POST or None)

    next_url = request.POST.get('next') or request.GET.get('next', '')
    if request.method == 'POST' and form.is_valid():
        user = form.save()
        login(request, user)
        messages.success(request, 'Your account has been created successfully.')
        if user.is_company:
            messages.info(request, 'Next, finish your company profile so professionals can learn about you.')
            return redirect(reverse('employers:edit_company'))
        if user.is_seeker and not next_url:
            messages.info(request, 'Next, build your professional profile so employers can find you.')
            return redirect(reverse('seekers:edit_profile'))
        return _redirect_for_user(user, next_url, request)

    context = {
        'account_type': account_type,
        'active_form': form,
        'next': next_url,
    }
    return render(request, 'accounts/register.html', context)


@login_required
def profile_redirect_view(request):
    return _redirect_for_user(request.user)


def _dashboard_url_for_user(user):
    if user.is_platform_admin:
        return reverse('platform_admin:dashboard')
    if user.is_company:
        return reverse('employers:dashboard')
    return reverse('seekers:dashboard')


def _seeker_profile_form(user, data=None, files=None):
    from accounts.profile_setup import unique_slug
    from seekers.forms import SeekerProfileForm
    from seekers.models import SeekerProfile

    profile = SeekerProfile.objects.filter(user=user).first()
    if profile:
        return SeekerProfileForm(data, files, instance=profile, prefix='seeker'), profile

    full_name = user.get_full_name().strip() or user.username
    return SeekerProfileForm(
        data,
        files,
        prefix='seeker',
        initial={
            'full_name': full_name,
            'headline': 'Job Seeker',
            'category': 'General',
            'location': 'Lusaka',
            'is_public': False,
        },
    ), profile


def _company_profile_form(user, data=None, files=None):
    from companies.models import Company
    from employers.forms import CompanyProfileForm

    company = Company.objects.filter(owner=user).first()
    if not company:
        return None, None
    return CompanyProfileForm(data, files, instance=company, prefix='company'), company


SEEKER_LAYOUT = [
    ('Your public profile', 'fa-id-card', ['full_name', 'headline', 'job_category', 'location']),
    ('Experience & availability', 'fa-briefcase', [
        'years_experience', 'experience_level', 'availability', 'qualification', 'salary_min', 'salary_max']),
    ('About you', 'fa-pen-to-square', ['skills', 'bio', 'education', 'experience_summary']),
    ('Your CV', 'fa-file-lines', ['cv_file']),
    ('Visibility', 'fa-eye', ['is_public', 'is_online']),
]
COMPANY_LAYOUT = [
    ('Company details', 'fa-building', ['name', 'industry', 'location', 'description']),
    ('Contact & web', 'fa-address-book', ['email', 'phone', 'website', 'linkedin_url']),
    ('Size & focus', 'fa-chart-pie', ['employee_count_display', 'company_size', 'founded_year', 'tags']),
    ('Applications', 'fa-inbox', ['application_delivery_method']),
    ('Visibility', 'fa-eye', ['is_public', 'is_online']),
]
ACTION_TAB = {'profile': 'account', 'password': 'security', 'seeker': 'profile', 'company': 'profile'}


def _group_fields(form, layout):
    """Arrange a form's fields into titled sections; anything not listed lands in 'More details'."""
    if form is None:
        return []
    used, sections = set(), []
    for title, icon, names in layout:
        fields = [form[n] for n in names if n in form.fields]
        used.update(n for n in names if n in form.fields)
        if fields:
            sections.append({'title': title, 'icon': icon, 'fields': fields})
    rest = [form[n] for n in form.fields if n not in used and n not in ('cover_image', 'clear_cover_image')]
    if rest:
        sections.append({'title': 'More details', 'icon': 'fa-ellipsis', 'fields': rest})
    return sections


def _settings_redirect(tab):
    return redirect(f"{reverse('accounts:settings')}?tab={tab}")


def _settings_portal_context(request, seeker_profile, company):
    """Everything the surrounding dashboard layout (sidebar, top bar) needs, per role."""
    user = request.user
    if user.is_platform_admin:
        from platform_admin.utils import admin_sidebar_context

        return 'platform_admin/base.html', {'admin_nav': 'settings', **admin_sidebar_context(user)}
    if user.is_company:
        from jobs.models import JobApplication
        from subscriptions.usage import can_company_access_cv_library

        plan = None
        subscription = getattr(company, 'subscription', None) if company else None
        if subscription and getattr(subscription, 'is_valid', False):
            plan = subscription.plan
        try:
            cv_allowed = can_company_access_cv_library(company, user)[0] if company else False
        except Exception:
            cv_allowed = False
        return 'employers/base_portal.html', {
            'company': company, 'employer_nav': 'settings', 'subscription_plan': plan, 'cv_access_allowed': cv_allowed,
            'total_applications': JobApplication.objects.filter(job__posted_by=user).count(),
        }
    from jobs.models import JobApplication
    from seekers.views import _seeker_subscription_plan

    return 'seekers/base_portal.html', {
        'profile': seeker_profile, 'seeker_nav': 'settings',
        'seeker_subscription_plan': _seeker_subscription_plan(user),
        'applications_count': JobApplication.objects.filter(applicant=user).count(),
    }


@login_required
@require_http_methods(['GET', 'POST'])
def account_settings_view(request):
    user = request.user
    profile_form = AccountSettingsForm(instance=user, prefix='profile')
    password_form = AccountPasswordChangeForm(user=user, prefix='password')
    seeker_form, seeker_profile = (None, None)
    company_form, company = (None, None)

    if user.is_seeker:
        seeker_form, seeker_profile = _seeker_profile_form(user)
    elif user.is_company:
        company_form, company = _company_profile_form(user)

    active_tab = request.GET.get('tab', 'account')

    if request.method == 'POST':
        action = request.POST.get('action')
        active_tab = ACTION_TAB.get(action, active_tab)

        if action == 'profile':
            profile_form = AccountSettingsForm(
                request.POST,
                request.FILES,
                instance=user,
                prefix='profile',
            )
            if profile_form.is_valid():
                profile_form.save()
                messages.success(request, 'Your account details have been updated.')
                return _settings_redirect('account')

        elif action == 'password':
            password_form = AccountPasswordChangeForm(
                user=user,
                data=request.POST,
                prefix='password',
            )
            if password_form.is_valid():
                updated_user = password_form.save()
                update_session_auth_hash(request, updated_user)
                messages.success(request, 'Your password has been changed.')
                return _settings_redirect('security')

        elif action == 'seeker' and user.is_seeker:
            seeker_form, seeker_profile = _seeker_profile_form(
                user,
                request.POST,
                request.FILES,
            )
            if seeker_form.is_valid():
                profile = seeker_form.save(commit=False)
                profile.user = user
                if not profile.slug:
                    from accounts.profile_setup import unique_slug
                    from seekers.models import SeekerProfile
                    profile.slug = unique_slug(SeekerProfile, profile.full_name)
                profile.save()
                messages.success(request, 'Your seeker profile has been updated.')
                return _settings_redirect('profile')

        elif action == 'company' and user.is_company:
            company_form, company = _company_profile_form(
                user,
                request.POST,
                request.FILES,
            )
            if company_form and company_form.is_valid():
                before = company_profile_snapshot(company)
                updated_company = company_form.save()
                if company_profile_changed(before, updated_company):
                    notify_followers_profile_updated(updated_company)
                messages.success(request, 'Your company profile has been updated.')
                return _settings_redirect('profile')

    has_profile_tab = bool(seeker_form or company_form)
    if active_tab not in ('account', 'security', 'profile') or (active_tab == 'profile' and not has_profile_tab):
        active_tab = 'account'

    base_template, portal_context = _settings_portal_context(request, seeker_profile, company)
    return render(request, 'accounts/settings.html', {
        **portal_context,
        'base_template': base_template,
        'active_tab': active_tab,
        'profile_form': profile_form,
        'password_form': password_form,
        'seeker_form': seeker_form,
        'seeker_profile': seeker_profile,
        'seeker_sections': _group_fields(seeker_form, SEEKER_LAYOUT),
        'company_form': company_form,
        'company': company,
        'company_sections': _group_fields(company_form, COMPANY_LAYOUT),
        'has_profile_tab': has_profile_tab,
        'dashboard_url': _dashboard_url_for_user(user),
    })


@login_required
def notifications_view(request):
    from django.core.paginator import Paginator
    from django.db.models import Count, Q

    from .events import GROUPS, TYPE_ICONS

    user = request.user
    base = UserNotification.objects.filter(recipient=user)
    tab = request.GET.get('tab', 'all')
    if tab not in ('all', 'unread', *GROUPS):
        tab = 'all'

    def group_q(name):
        return Q(notification_type__in=GROUPS[name])

    items = base
    if tab == 'unread':
        items = items.filter(is_read=False)
    elif tab in GROUPS:
        items = items.filter(group_q(tab))
    q = request.GET.get('q', '').strip()
    if q:
        items = items.filter(Q(title__icontains=q) | Q(body__icontains=q) | Q(message__icontains=q))

    counts = base.aggregate(
        all=Count('id'), unread=Count('id', filter=Q(is_read=False)),
        **{name: Count('id', filter=group_q(name)) for name in GROUPS},
        **{f'{name}_unread': Count('id', filter=group_q(name) & Q(is_read=False)) for name in GROUPS},
    )
    paginator = Paginator(items.order_by('-created_at'), 20)
    page = paginator.get_page(request.GET.get('page'))
    page_range = paginator.get_elided_page_range(page.number, on_each_side=1, on_ends=1)
    for note in page:
        note.icon = TYPE_ICONS.get(note.notification_type, 'fa-bell')

    seeker_profile = company = None
    if user.is_seeker:
        _form, seeker_profile = _seeker_profile_form(user)
    elif user.is_company:
        _form, company = _company_profile_form(user)
    base_template, portal_context = _settings_portal_context(request, seeker_profile, company)
    if user.is_platform_admin:
        portal_context['admin_nav'] = 'notifications'
    elif user.is_company:
        portal_context['employer_nav'] = 'notifications'
    else:
        portal_context['seeker_nav'] = 'notifications'

    query = request.GET.copy()
    query.pop('page', None)
    return render(request, 'accounts/notifications.html', {
        **portal_context,
        'base_template': base_template,
        'notifications': page,
        'page_obj': page,
        'query_string': query.urlencode(),
        'page_range': page_range,
        'counts': counts,
        'active_tab': tab,
        'search_query': q,
        'dashboard_url': _dashboard_url_for_user(user),
    })


def _notification_or_404(request, pk):
    from django.shortcuts import get_object_or_404

    return get_object_or_404(UserNotification, pk=pk, recipient=request.user)


def _back_to_list(request):
    target = request.POST.get('next') or reverse('accounts:notifications')
    if not url_has_allowed_host_and_scheme(target, allowed_hosts={request.get_host()}):
        target = reverse('accounts:notifications')
    return redirect(target)


@login_required
def notification_open(request, pk):
    note = _notification_or_404(request, pk)
    if not note.is_read:
        UserNotification.objects.filter(pk=note.pk).update(is_read=True)
    target = note.link_path
    if target and url_has_allowed_host_and_scheme(target, allowed_hosts={request.get_host()}):
        return redirect(target)
    return redirect('accounts:notifications')


@login_required
@require_http_methods(['POST'])
def notification_toggle_read(request, pk):
    note = _notification_or_404(request, pk)
    UserNotification.objects.filter(pk=note.pk).update(is_read=not note.is_read)
    return _back_to_list(request)


@login_required
@require_http_methods(['POST'])
def notification_delete(request, pk):
    _notification_or_404(request, pk).delete()
    return _back_to_list(request)


@login_required
@require_http_methods(['POST'])
def notifications_mark_all_read(request):
    UserNotification.objects.filter(recipient=request.user, is_read=False).update(is_read=True)
    messages.success(request, 'All notifications marked as read.')
    return _back_to_list(request)


@login_required
@require_http_methods(['POST'])
def notifications_clear_read(request):
    deleted, _ = UserNotification.objects.filter(recipient=request.user, is_read=True).delete()
    messages.success(request, f'Removed {deleted} read notification{"" if deleted == 1 else "s"}.')
    return _back_to_list(request)