import { Controller, Get, Query, Req, Res, Logger } from '@nestjs/common';
import type { Request, Response } from 'express';
import { PrismaService } from '../common/prisma.service';
import { MarcadeoProvisioning } from '../integrations/marcadeo-provisioning.service';
import { ShopifyAuthService } from './shopify-auth.service';

const STATE_COOKIE = 'shopify_oauth_state';

/**
 * Shopify OAuth install flow for the embedded public app. Reachable at the
 * app's public URL: GET /api/auth?shop=xxx starts it, GET /api/auth/callback
 * finishes it (token exchange + webhooks + Marcadeo advertiser).
 */
@Controller('auth')
export class AuthController {
  private readonly log = new Logger('Auth');

  constructor(
    private prisma: PrismaService,
    private auth: ShopifyAuthService,
    private marcadeo: MarcadeoProvisioning,
  ) {}

  /** Step 1 — begin install: redirect the merchant to Shopify's consent screen. */
  @Get()
  async begin(@Query('shop') shop: string, @Res() res: Response) {
    this.log.log(`[begin] hit — shop=${shop}`);
    if (!this.auth.configured()) {
      this.log.warn('[begin] NOT configured (SHOPIFY_API_KEY/SECRET/APP_URL missing)');
      return res.status(503).send('Shopify OAuth not configured (set SHOPIFY_API_KEY/SECRET/APP_URL).');
    }
    if (!this.auth.validShop(shop)) {
      this.log.warn(`[begin] invalid shop: ${shop}`);
      return res.status(400).send('Missing or invalid ?shop=xxx.myshopify.com');
    }
    const state = this.auth.newState();
    this.log.log(`[begin] redirecting ${shop} to Shopify OAuth consent`);
    // Short-lived, http-only state cookie to guard the callback against CSRF.
    res.cookie(STATE_COOKIE, state, {
      httpOnly: true,
      secure: true,
      sameSite: 'lax',
      maxAge: 10 * 60 * 1000,
    });
    return res.redirect(this.auth.authUrl(shop, state));
  }

  /** Step 2 — Shopify redirects back here with a code; finish the install. */
  @Get('callback')
  async callback(@Query() query: Record<string, any>, @Req() req: Request, @Res() res: Response) {
    const shop = query.shop as string;
    this.log.log(`[callback] hit — shop=${shop}, hasCode=${!!query.code}, hasHmac=${!!query.hmac}`);
    if (!this.auth.configured()) {
      this.log.warn('[callback] NOT configured');
      return res.status(503).send('Shopify OAuth not configured.');
    }
    if (!this.auth.validShop(shop)) {
      this.log.warn(`[callback] invalid shop: ${shop}`);
      return res.status(400).send('Invalid shop.');
    }
    if (!this.auth.verifyCallbackHmac(query)) {
      this.log.warn(`[callback] HMAC FAILED for ${shop}`);
      return res.status(400).send('HMAC verification failed.');
    }

    // CSRF: the state we set must match the one Shopify echoed back.
    const cookies = this.parseCookies(req.headers.cookie);
    if (!query.state || cookies[STATE_COOKIE] !== query.state) {
      this.log.warn(`[callback] STATE MISMATCH for ${shop} (cookie=${cookies[STATE_COOKIE]}, query=${query.state})`);
      return res.status(400).send('State mismatch — please retry the install.');
    }
    res.clearCookie(STATE_COOKIE);

    try {
      this.log.log(`[callback] exchanging token for ${shop}`);
      const { accessToken, scope } = await this.auth.exchangeToken(shop, String(query.code));
      this.log.log(`[callback] token OK for ${shop} (scopes: ${scope})`);

      // Store the per-shop token (this replaces the static admin token model).
      const merchant = await this.prisma.merchant.upsert({
        where: { shop },
        create: {
          shop,
          programName: shop.replace('.myshopify.com', ''),
          shopifyAccessToken: accessToken,
          shopifyScopes: scope,
          appInstalled: true,
          installedAt: new Date(),
        },
        update: {
          shopifyAccessToken: accessToken,
          shopifyScopes: scope,
          appInstalled: true,
          installedAt: new Date(),
          uninstalledAt: null,
        },
      });

      this.log.log(`[callback] merchant saved: ${merchant.shop} (id=${merchant.id})`);

      // Register webhooks + storefront script + provision Marcadeo (all fail-soft).
      await this.auth.registerWebhooks(shop, accessToken);
      await this.auth.registerScriptTag(shop, accessToken);
      const advertiserId = await this.marcadeo.ensureAdvertiser(merchant);
      this.log.log(`[callback] marcadeo advertiser: ${advertiserId ?? 'FAILED/null'}`);

      this.log.log(`[callback] ✅ installed ${shop}`);
      // Open the embedded app inside Shopify admin.
      return res.redirect(`https://${shop}/admin/apps/${this.auth.apiKey}`);
    } catch (e) {
      this.log.error(`[callback] install FAILED for ${shop}: ${(e as Error).message}`);
      return res.status(500).send('Install failed. Please try again.');
    }
  }

  private parseCookies(header?: string): Record<string, string> {
    const out: Record<string, string> = {};
    if (!header) return out;
    for (const part of header.split(';')) {
      const i = part.indexOf('=');
      if (i > -1) out[part.slice(0, i).trim()] = decodeURIComponent(part.slice(i + 1).trim());
    }
    return out;
  }
}
