use oxc_ast::{
    AstKind,
    ast::{
        Expression, JSXAttributeItem, JSXAttributeValue, JSXElementName, JSXExpression,
        JSXOpeningElement, ObjectProperty, ObjectPropertyKind, PropertyKey,
    },
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;

use crate::{
    AstNode,
    context::LintContext,
    rule::Rule,
    utils::{get_string_literal_prop_value, has_jsx_prop_ignore_case},
};

fn next_script_for_ga_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn(
        "Prefer `next/script` component when using the inline script for Google Analytics.",
    )
    .with_help("See https://nextjs.org/docs/messages/next-script-for-ga")
    .with_label(span)
}

#[derive(Debug, Default, Clone)]
pub struct NextScriptForGa;

declare_oxc_lint!(
    /// ### What it does
    ///
    /// Enforces the use of the `next/script` component when implementing Google Analytics in Next.js applications,
    /// instead of using regular `<script>` tags.
    ///
    /// ### Why is this bad?
    ///
    /// Using regular `<script>` tags for Google Analytics can lead to several issues:
    /// - Scripts may load in a blocking manner, impacting page performance
    /// - No built-in optimization or loading strategies
    /// - Lack of automatic resource handling that Next.js provides
    ///
    /// ### Examples
    ///
    /// Examples of **incorrect** code for this rule:
    /// ```jsx
    /// // Using regular script tag with GA source
    /// <script src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
    ///
    /// // Using inline script for GA initialization
    /// <script dangerouslySetInnerHTML={{
    ///   __html: `
    ///     window.dataLayer = window.dataLayer || [];
    ///     function gtag(){dataLayer.push(arguments);}
    ///     gtag('js', new Date());
    ///     gtag('config', 'GA_MEASUREMENT_ID');
    ///   `
    /// }} />
    /// ```
    ///
    /// Examples of **correct** code for this rule:
    /// ```jsx
    /// import Script from 'next/script'
    ///
    /// // Using next/script for GA source
    /// <Script
    ///   src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
    ///   strategy="lazyOnload"
    /// />
    ///
    /// // Using next/script for GA initialization
    /// <Script id="google-analytics">
    ///   {`
    ///     window.dataLayer = window.dataLayer || [];
    ///     function gtag(){dataLayer.push(arguments);}
    ///     gtag('js', new Date());
    ///     gtag('config', 'GA_MEASUREMENT_ID');
    ///   `}
    /// </Script>
    /// ```
    NextScriptForGa,
    nextjs,
    correctness,
    version = "0.2.0",
    short_description = "Enforces the use of the `next/script` component when implementing Google Analytics in Next.js applications, instead of using regular `<script>` tags.",
);

impl Rule for NextScriptForGa {
    fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
        let AstKind::JSXOpeningElement(jsx_opening_element) = node.kind() else {
            return;
        };

        let JSXElementName::Identifier(jsx_opening_element_name) = &jsx_opening_element.name else {
            return;
        };

        if jsx_opening_element_name.name.as_str() != "script" {
            return;
        }

        // Check if the Alternative async tag is being used to add GA.
        // https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag
        // https://developers.google.com/analytics/devguides/collection/gtagjs
        if let Some(src_prop) = has_jsx_prop_ignore_case(jsx_opening_element, "src")
            && let Some(src_prop_value) = get_string_literal_prop_value(src_prop)
            && SUPPORTED_SRCS.iter().any(|s| src_prop_value.contains(s))
        {
            ctx.diagnostic(next_script_for_ga_diagnostic(jsx_opening_element_name.span));
            return;
        }

        // Check if inline script is being used to add GA.
        // https://developers.google.com/analytics/devguides/collection/analyticsjs#the_google_analytics_tag
        // https://developers.google.com/tag-manager/quickstart
        if let Some(danger_value) = get_dangerously_set_inner_html_prop_value(jsx_opening_element) {
            let Expression::TemplateLiteral(template_literal) = &danger_value.value else {
                return;
            };
            let template_literal = template_literal.quasis[0].value.raw.as_str();
            if SUPPORTED_HTML_CONTENT_URLS.iter().any(|s| template_literal.contains(s)) {
                ctx.diagnostic(next_script_for_ga_diagnostic(jsx_opening_element_name.span));
            }
        }
    }
}

const SUPPORTED_SRCS: [&str; 2] =
    ["www.google-analytics.com/analytics.js", "www.googletagmanager.com/gtag/js"];

const SUPPORTED_HTML_CONTENT_URLS: [&str; 2] =
    ["www.google-analytics.com/analytics.js", "www.googletagmanager.com/gtm.js"];

fn get_dangerously_set_inner_html_prop_value<'a>(
    jsx_opening_element: &'a JSXOpeningElement<'a>,
) -> Option<&'a ObjectProperty<'a>> {
    let Some(JSXAttributeItem::Attribute(dangerously_set_inner_html_prop)) =
        has_jsx_prop_ignore_case(jsx_opening_element, "dangerouslysetinnerhtml")
    else {
        return None;
    };
    let Some(JSXAttributeValue::ExpressionContainer(object_expr)) =
        &dangerously_set_inner_html_prop.value
    else {
        return None;
    };
    let JSXExpression::ObjectExpression(object_expr) = &object_expr.expression else {
        return None;
    };

    if let Some(html_prop) = object_expr.properties.iter().find_map(|prop| {
        if let ObjectPropertyKind::ObjectProperty(html_prop) = prop {
            if let PropertyKey::StaticIdentifier(html_prop_ident) = &html_prop.key {
                if html_prop_ident.name == "__html" { Some(html_prop) } else { None }
            } else {
                None
            }
        } else {
            None
        }
    }) {
        return Some(html_prop);
    }

    None
}

#[test]
fn test() {
    use crate::tester::Tester;

    let pass = vec![
        r#"import Script from 'next/script'

			      export class Blah extends Head {
			        render() {
			          return (
			            <div>
			              <h1>Hello title</h1>
			              <Script
			                src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
			                strategy="lazyOnload"
			              />
			              <Script id="google-analytics">
			                {`
			                  window.dataLayer = window.dataLayer || [];
			                  function gtag(){window.dataLayer.push(arguments);}
			                  gtag('js', new Date());

			                  gtag('config', 'GA_MEASUREMENT_ID');
			                `}
			              </Script>
			            </div>
			          );
			        }
			    }"#,
        r#"import Script from 'next/script'

			      export class Blah extends Head {
			        render() {
			          return (
			            <div>
			              <h1>Hello title</h1>
			              <Script id="google-analytics">
			                {`(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
			                    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
			                    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
			                    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

			                    ga('create', 'UA-XXXXX-Y', 'auto');
			                    ga('send', 'pageview');
			                })`}
			              </Script>
			            </div>
			          );
			        }
			    }"#,
        r#"import Script from 'next/script'

			        export class Blah extends Head {
			        render() {
			            return (
			            <div>
			                <h1>Hello title</h1>
			                <Script id="google-analytics">
			                    {`window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
			                    ga('create', 'UA-XXXXX-Y', 'auto');
			                    ga('send', 'pageview');
			                    })`}
			                </Script>
			            </div>
			            );
			        }
			    }"#,
        r"export class Blah extends Head {
			          render() {
			            return (
			              <div>
			                <h1>Hello title</h1>
			                <script dangerouslySetInnerHTML={{}} />
			              </div>
			            );
			          }
			      }",
    ];

    let fail = vec![
        r"
			        export class Blah extends Head {
			          render() {
			            return (
			              <div>
			                <h1>Hello title</h1>
			                <script async src='https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}' />
			                <script
			                  dangerouslySetInnerHTML={{
			                    __html: `
			                      window.dataLayer = window.dataLayer || [];
			                      function gtag(){dataLayer.push(arguments);}
			                      gtag('js', new Date());
			                      gtag('config', '${GA_TRACKING_ID}', {
			                        page_path: window.location.pathname,
			                      });
			                  `,
			                }}/>
			              </div>
			            );
			          }
			      }",
        r"
			        export class Blah extends Head {
			          render() {
			            return (
			              <div>
			                <h1>Hello title</h1> qqq
			                {/* Google Tag Manager - Global base code */}
			                <script
			                dangerouslySetInnerHTML={{
			                  __html: `
			                    (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
			                    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
			                    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
			                    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
			                    })(window,document,'script','dataLayer', '${GTM_ID}');
			                  `,
			                }}/>
			              </div>
			            );
			          }
			      }",
        r"
			        export class Blah extends Head {
			          render() {
			            return (
			              <div>
			                <h1>Hello title</h1>
			                <script dangerouslySetInnerHTML={{
			                    __html: `
			                      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
			                        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
			                        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
			                        })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

			                        ga('create', 'UA-XXXXX-Y', 'auto');
			                        ga('send', 'pageview');
			                    `,
			                  }}/>
			              </div>
			            );
			          }
			      }",
        r"
			        export class Blah extends Head {
			          render() {
			            return (
			              <div>
			                <h1>Hello title</h1>
			                <script dangerouslySetInnerHTML={{
			                    __html: `
			                        window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
			                        ga('create', 'UA-XXXXX-Y', 'auto');
			                        ga('send', 'pageview');
			                    `,
			                  }}/>
			                <script async src='https://www.google-analytics.com/analytics.js'></script>
			              </div>
			            );
			          }
			      }",
        r"
			        export class Blah extends Head {
			          createGoogleAnalyticsMarkup() {
			            return {
			              __html: `
			                window.dataLayer = window.dataLayer || [];
			                function gtag(){dataLayer.push(arguments);}
			                gtag('js', new Date());
			                gtag('config', 'UA-148481588-2');`,
			            };
			          }

			          render() {
			            return (
			              <div>
			                <h1>Hello title</h1>
			                <script dangerouslySetInnerHTML={this.createGoogleAnalyticsMarkup()} />
			                <script async src='https://www.google-analytics.com/analytics.js'></script>
			              </div>
			            );
			          }
			      }",
    ];

    Tester::new(NextScriptForGa::NAME, NextScriptForGa::PLUGIN, pass, fail).test_and_snapshot();
}
