from django.shortcuts import render, get_object_or_404
from django.core.paginator import Paginator
from django.db import models
from django.db.models import Prefetch
from .models import (
    SiteSettings,
    HomeHero, HomeSlide, HomeCounter, HomeServices,
    HomeAbout, HomeFounder, HomeCTA1, HomeCTA2, HomeProcess,
    HomeWhyChooseUs, WhyChooseUsBox, HomeTestimonials,
    Testimonial, Service, ServiceCategory, PartnerLogo, LegalPage,
)
from apps.portfolio.models import PortfolioItem, PortfolioCategory
from apps.insights.models import Insight, InsightCategory
from apps.faq.models import FAQCategory, FAQItem
from apps.contact.models import ContactEnquiry


def _t(request):
    """Return request.tenant (or None for master site)."""
    return getattr(request, 'tenant', None)


def home(request):
    t = _t(request)
    hero                 = HomeHero.load(t)
    # Panels of the moving hero. Empty on most sites, and a theme whose hero
    # does not move ignores it, so this costs one query and changes nothing
    # until somebody adds a slide.
    slides               = HomeSlide.objects.filter(is_active=True)
    counters             = HomeCounter.objects.filter(is_active=True)
    services_header      = HomeServices.load(t)
    about                = HomeAbout.load(t)
    # A theme with no founder block simply never reads this.
    founder              = HomeFounder.load(t)
    cta1                 = HomeCTA1.load(t)
    cta2                 = HomeCTA2.load(t)
    process              = HomeProcess.load(t)
    process_steps        = [
        (1, process.step1_title, process.step1_body, process.step1_icon),
        (2, process.step2_title, process.step2_body, process.step2_icon),
        (3, process.step3_title, process.step3_body, process.step3_icon),
    ]
    why                  = HomeWhyChooseUs.load(t)
    why_boxes            = WhyChooseUsBox.objects.filter(is_active=True)
    testimonials_section = HomeTestimonials.load(t)
    testimonials         = Testimonial.objects.filter(is_active=True, show_on_home=True).order_by('order')
    hero_avatars         = Testimonial.objects.filter(is_active=True).exclude(photo='').exclude(photo__isnull=True).order_by('-created_at')[:4]
    services             = Service.objects.filter(is_active=True, show_on_home=True)
    partners             = PartnerLogo.objects.filter(is_active=True, logo_type='partner')
    # The homepage row, not the whole portfolio.
    #
    # This used to be six by recency, while the section below it rendered all
    # of them behind filter tabs. That put every image on the homepage, made
    # the filters a control most visitors scroll past, and left the real
    # /portfolio/ page with nothing to do.
    #
    # Sixteen fills four rows of four exactly. A site that has not ticked
    # anything falls back to the first sixteen by order rather than showing an
    # empty grid, so this changes nothing until somebody chooses.
    _picked = PortfolioItem.objects.filter(is_active=True, show_on_home=True)\
                                   .select_related('category').order_by('home_order', 'title')
    portfolio            = list(_picked[:16]) or list(
        PortfolioItem.objects.filter(is_active=True)
                             .select_related('category').order_by('home_order', 'title')[:16]
    )
    portfolio_total      = PortfolioItem.objects.filter(is_active=True).count()
    insights             = Insight.objects.filter(status='published', is_visible=True).exclude(thumbnail='').order_by('-published_at')[:3]

    # A homepage that shows the whole portfolio behind category filters needs
    # all of it, not the six most recent. Added alongside the six rather than
    # replacing them, so a template already using 'portfolio' keeps the row it
    # was built for and nothing on a live site moves.
    portfolio_all        = PortfolioItem.objects.filter(is_active=True).select_related('category')\
                                        .order_by('home_order', 'title')
    portfolio_categories = PortfolioCategory.objects.filter(
                               portfolioitem__is_active=True).distinct().order_by('order', 'name')

    return render(request, 'public/index.html', {
        'hero': hero, 'slides': slides, 'counters': counters,
        'services_header': services_header, 'about': about, 'cta1': cta1, 'cta2': cta2,
        'founder': founder,
        'process': process, 'process_steps': process_steps, 'why': why, 'why_boxes': why_boxes,
        'testimonials_section': testimonials_section, 'testimonials': testimonials, 'hero_avatars': hero_avatars,
        'services': services, 'partners': partners,
        'portfolio': portfolio, 'portfolio_total': portfolio_total, 'insights': insights,
        'portfolio_all': portfolio_all, 'portfolio_categories': portfolio_categories,
    })


def services_page(request):
    category_slug   = request.GET.get('category')
    active_category = None
    services        = Service.objects.filter(is_active=True).select_related('category').order_by('order')
    categories      = ServiceCategory.objects.all()

    if category_slug:
        active_category = ServiceCategory.objects.filter(slug=category_slug).first()
        if active_category:
            services = services.filter(category=active_category)

    paginator = Paginator(services, 12)
    page_obj  = paginator.get_page(request.GET.get('page'))

    if request.GET.get('partial') == '1':
        return render(request, 'public/partials/service_cards.html', {'services': page_obj})

    return render(request, 'public/services.html', {
        'services': page_obj,
        'categories': categories,
        'active_category': active_category,
    })


def service_detail(request, slug):
    service  = get_object_or_404(Service, slug=slug, is_active=True, service_page_active=True)
    related  = Service.objects.filter(is_active=True).exclude(pk=service.pk).order_by('order')[:4]
    return render(request, 'public/service_detail.html', {
        'service': service,
        'related': related,
    })


def portfolio_list(request):
    category_slug   = request.GET.get('category')
    used_cat_ids    = PortfolioItem.objects.filter(is_active=True).values_list('category_id', flat=True).distinct()
    categories      = PortfolioCategory.objects.filter(id__in=used_cat_ids)
    active_category = None
    # Alphabetical. A directory of 39 brands is something people scan for a
    # name, so recency and featured flags only make it harder to find one.
    items           = PortfolioItem.objects.filter(is_active=True).exclude(thumbnail='').order_by('title')

    if category_slug:
        active_category = PortfolioCategory.objects.filter(slug=category_slug).first()
        if active_category:
            items = items.filter(category=active_category)

    # The portfolio page lists the whole portfolio.
    #
    # It used to paginate nine at a time, so a site with 39 brands showed nine
    # and the rest were only reachable through a page parameter no link
    # pointed at. The homepage row is where a selection belongs, and that is
    # handled separately in home().
    #
    # Pagination is kept behind a page parameter for anyone who has bookmarked
    # one, so ?page=2 still works, but the default is everything.
    if request.GET.get('page'):
        paginator = Paginator(items, 9)
        items = paginator.get_page(request.GET.get('page'))

    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        return render(request, 'public/partials/portfolio_cards.html', {'items': items})

    return render(request, 'public/portfolio.html', {
        'items': items, 'categories': categories, 'active_category': active_category,
    })


def _active_insight_categories():
    """Only categories that have at least one published post."""
    used_ids = Insight.objects.filter(status='published').values_list('category_id', flat=True).distinct()
    return InsightCategory.objects.filter(id__in=used_ids)


def insights_list(request):
    from apps.auction.models import AuctionListing
    category_slug   = request.GET.get('category')
    categories      = _active_insight_categories()
    active_category = None
    posts           = Insight.objects.filter(status='published', is_visible=True).order_by('-published_at')

    if category_slug:
        active_category = InsightCategory.objects.filter(slug=category_slug).first()
        if active_category:
            posts = posts.filter(category=active_category)

    paginator = Paginator(posts, 12)
    page_obj  = paginator.get_page(request.GET.get('page'))

    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        return render(request, 'public/partials/insights_cards.html', {'posts': page_obj})

    popular_posts = Insight.objects.filter(status='published').order_by('-view_count', '-published_at')[:3]
    top_domains = AuctionListing.objects.filter(
        status=AuctionListing.STATUS_ACTIVE,
        asset_type=AuctionListing.TYPE_DOMAIN,
    ).order_by('-is_premium', '-view_count')[:5]

    return render(request, 'public/insights.html', {
        'posts': page_obj,
        'categories': categories,
        'active_category': active_category,
        'popular_posts': popular_posts,
        'top_domains': top_domains,
    })


def insight_detail(request, slug):
    import json as _json
    from apps.auction.models import AuctionListing

    post = get_object_or_404(Insight, slug=slug, status='published', is_visible=True, tenant=_t(request))
    Insight.objects.filter(pk=post.pk).update(view_count=models.F('view_count') + 1)
    # Three more from the same category, newest first, so the sidebar keeps
    # the reader on the subject they came for. Falls back to the newest
    # posts overall when the category has nothing else in it yet, rather
    # than showing an empty panel.
    _base = Insight.objects.filter(status='published').exclude(pk=post.pk).exclude(thumbnail='')
    related = []
    if post.category_id:
        related = list(_base.filter(category_id=post.category_id).order_by('-published_at')[:3])
    if not related:
        related = list(_base.order_by('-published_at')[:3])
    categories = _active_insight_categories()
    domain_suggestions = AuctionListing.objects.filter(
        status=AuctionListing.STATUS_ACTIVE,
        asset_type=AuctionListing.TYPE_DOMAIN,
    ).order_by('-is_premium', '-view_count')[:5]

    # ── SEO / GEO context ──────────────────────────────────────────────────────
    page_url   = request.build_absolute_uri()
    canonical  = post.canonical_url or page_url
    meta_title = post.meta_title or post.title
    meta_desc  = post.meta_description or post.excerpt[:160]
    og_image   = post.og_image_url or (post.thumbnail.url if post.thumbnail else '')

    # Parse FAQ pairs
    faq_items = []
    if post.faq_json:
        try:
            faq_items = [i for i in _json.loads(post.faq_json) if i.get('q') and i.get('a')]
        except (ValueError, TypeError):
            faq_items = []

    # Parse sources
    sources = []
    if post.sources_json:
        try:
            sources = [s for s in _json.loads(post.sources_json) if s.get('url')]
        except (ValueError, TypeError):
            sources = []

    # ── Build JSON-LD schemas ──────────────────────────────────────────────────
    base_url = f"{request.scheme}://{request.get_host()}"

    article_schema = {
        "@context": "https://schema.org",
        "@type": "Article",
        "headline": meta_title,
        "description": meta_desc,
        "mainEntityOfPage": canonical,
        "url": canonical,
    }
    if og_image:
        article_schema["image"] = og_image
    if post.published_at:
        article_schema["datePublished"] = post.published_at.date().isoformat()
    if post.update_date:
        article_schema["dateModified"] = post.update_date.isoformat()
    elif post.published_at:
        article_schema["dateModified"] = post.published_at.date().isoformat()
    if post.author:
        person = {"@type": "Person", "name": post.author.get_full_name() or post.author.username}
        if post.author_title:  person["jobTitle"]    = post.author_title
        if post.author_bio:    person["description"] = post.author_bio
        if post.author_linkedin: person["sameAs"]   = [post.author_linkedin]
        article_schema["author"] = person
    if sources:
        article_schema["citation"] = [f"{s['title']}: {s['url']}" if s.get('title') else s['url'] for s in sources]

    schemas = [article_schema]

    # Breadcrumb
    crumbs = [
        {"@type": "ListItem", "position": 1, "name": "Home", "item": base_url + "/"},
        {"@type": "ListItem", "position": 2, "name": "Insights", "item": base_url + "/insights/"},
    ]
    pos = 3
    if post.category:
        crumbs.append({"@type": "ListItem", "position": pos, "name": post.category.name,
                        "item": base_url + f"/insights/?category={post.category.slug}"})
        pos += 1
    crumbs.append({"@type": "ListItem", "position": pos, "name": post.title, "item": canonical})
    schemas.append({
        "@context": "https://schema.org",
        "@type": "BreadcrumbList",
        "itemListElement": crumbs,
    })

    # FAQ schema
    if faq_items:
        schemas.append({
            "@context": "https://schema.org",
            "@type": "FAQPage",
            "mainEntity": [
                {"@type": "Question", "name": i["q"],
                 "acceptedAnswer": {"@type": "Answer", "text": i["a"]}}
                for i in faq_items
            ],
        })

    jsonld = _json.dumps(schemas, ensure_ascii=False)

    return render(request, 'public/insight_detail.html', {
        'post': post,
        'related': related,
        'categories': categories,
        'domain_suggestions': domain_suggestions,
        'faq_items': faq_items,
        'sources': sources,
        'jsonld': jsonld,
        'canonical_url': canonical,
        'meta_title': meta_title,
        'meta_desc': meta_desc,
        'og_image': og_image,
        'page_url': page_url,
    })


def contact(request):
    form_submitted = False
    form_error     = None
    form_data      = {}

    if request.method == 'POST':
        name    = request.POST.get('name', '').strip()
        email   = request.POST.get('email', '').strip()
        phone   = request.POST.get('phone', '').strip()
        subject = request.POST.get('subject', '').strip()
        message = request.POST.get('message', '').strip()

        # A theme may collect more than the record holds, company and job title
        # for instance. Those ride at the top of the message rather than being
        # dropped, so nothing the visitor typed is lost and no column is added
        # for a field only one site asks for.
        extras = []
        for field, label in (('company', 'Company'), ('position', 'Position')):
            value = request.POST.get(field, '').strip()
            if value:
                extras.append(f'{label}: {value}')
        if extras:
            message = '\n'.join(extras) + '\n\n' + message

        if not name or not email or not message:
            form_error = 'Please fill in your name, email and message.'
            form_data  = request.POST
        else:
            try:
                enquiry = ContactEnquiry.objects.create(
                    tenant=_t(request),
                    name=name, email=email, phone=phone,
                    subject=subject or 'Website Enquiry', message=message,
                )
                form_submitted = True
                _notify_enquiry(request, enquiry)
            except Exception as e:
                form_error = 'Something went wrong. Please try again or email us directly.'

    # The FAQ below the form comes from the panel rather than the template, so
    # the questions can be edited without a deploy. A theme with no FAQ section
    # simply never reads this.
    faq_categories = FAQCategory.objects.filter(is_active=True).prefetch_related(
        Prefetch('items', queryset=FAQItem.objects.filter(is_active=True).order_by('order', 'id'))
    ).order_by('order', 'name')

    return render(request, 'public/contact.html', {
        'form_submitted': form_submitted,
        'form_error':     form_error,
        'form_data':      form_data,
        'faq_categories': faq_categories,
    })


def _notify_enquiry(request, enquiry):
    """Email the enquiry to the address in Settings.

    Never allowed to break the visitor's experience: the record is already
    saved by the time this runs, so a mail server having a bad day costs a
    notification, not the enquiry itself.
    """
    from django.core.mail import send_mail
    try:
        site = SiteSettings.load(_t(request))
        to = (getattr(site, 'support_email', '') or getattr(site, 'email', '') or '').strip()
        if not to:
            return
        send_mail(
            subject=f'New enquiry: {enquiry.subject} — {enquiry.name}',
            message=(
                f'Name    : {enquiry.name}\n'
                f'Email   : {enquiry.email}\n'
                f'Phone   : {enquiry.phone or "not given"}\n'
                f'Subject : {enquiry.subject}\n\n'
                f'{enquiry.message}\n\n'
                f'Received {enquiry.created_at:%d %B %Y at %H:%M}\n'
            ),
            from_email=to,
            recipient_list=[to],
            fail_silently=True,
        )
    except Exception:
        pass


def faq(request):
    categories = FAQCategory.objects.prefetch_related('items').all()
    return render(request, 'public/faq.html', {'categories': categories})


def assessment(request):
    return render(request, 'public/assessment.html')


def startup(request):
    return render(request, 'public/startup.html')


# ── Legal pages ────────────────────────────────────────────────────────────────

def _legal(request, slug):
    from apps.pages.context_processors import _resolve_tenant
    from django.http import Http404
    tenant = _resolve_tenant(request)
    page = LegalPage.load(slug, tenant)
    if not page.is_visible:
        raise Http404
    return render(request, 'public/legal.html', {'page': page})

def terms(request):
    return _legal(request, 'terms')

def privacy_policy(request):
    return _legal(request, 'privacy-policy')

def cookie_policy(request):
    return _legal(request, 'cookie-policy')

def purchase_policy(request):
    return _legal(request, 'purchase-policy')
